@hed-hog/finance 0.0.350 → 0.0.351
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 +6 -0
- package/dist/dto/create-financial-title.dto.d.ts.map +1 -1
- package/dist/dto/create-financial-title.dto.js +27 -1
- package/dist/dto/create-financial-title.dto.js.map +1 -1
- package/dist/finance-data.controller.d.ts +12 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.d.ts +120 -0
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-realtime.controller.d.ts +7 -0
- package/dist/finance-realtime.controller.d.ts.map +1 -0
- package/dist/finance-realtime.controller.js +34 -0
- package/dist/finance-realtime.controller.js.map +1 -0
- package/dist/finance-realtime.service.d.ts +36 -0
- package/dist/finance-realtime.service.d.ts.map +1 -0
- package/dist/finance-realtime.service.js +59 -0
- package/dist/finance-realtime.service.js.map +1 -0
- package/dist/finance-statements.controller.d.ts +6 -0
- package/dist/finance-statements.controller.d.ts.map +1 -1
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +28 -0
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +142 -1
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +134 -19
- package/dist/finance.service.js.map +1 -1
- package/dist/mcp-tools/finance-audit-logs.mcp-tools.d.ts +8 -0
- package/dist/mcp-tools/finance-audit-logs.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-audit-logs.mcp-tools.js +60 -0
- package/dist/mcp-tools/finance-audit-logs.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-bank-accounts.mcp-tools.d.ts +16 -0
- package/dist/mcp-tools/finance-bank-accounts.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-bank-accounts.mcp-tools.js +121 -0
- package/dist/mcp-tools/finance-bank-accounts.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-categories.mcp-tools.d.ts +20 -0
- package/dist/mcp-tools/finance-categories.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-categories.mcp-tools.js +135 -0
- package/dist/mcp-tools/finance-categories.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-collections.mcp-tools.d.ts +16 -0
- package/dist/mcp-tools/finance-collections.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-collections.mcp-tools.js +91 -0
- package/dist/mcp-tools/finance-collections.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-cost-centers.mcp-tools.d.ts +16 -0
- package/dist/mcp-tools/finance-cost-centers.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-cost-centers.mcp-tools.js +114 -0
- package/dist/mcp-tools/finance-cost-centers.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-currencies.mcp-tools.d.ts +16 -0
- package/dist/mcp-tools/finance-currencies.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-currencies.mcp-tools.js +120 -0
- package/dist/mcp-tools/finance-currencies.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-data.mcp-tools.d.ts +15 -0
- package/dist/mcp-tools/finance-data.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-data.mcp-tools.js +80 -0
- package/dist/mcp-tools/finance-data.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-installments.mcp-tools.d.ts +93 -0
- package/dist/mcp-tools/finance-installments.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-installments.mcp-tools.js +646 -0
- package/dist/mcp-tools/finance-installments.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-period-close.mcp-tools.d.ts +9 -0
- package/dist/mcp-tools/finance-period-close.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-period-close.mcp-tools.js +79 -0
- package/dist/mcp-tools/finance-period-close.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-reports.mcp-tools.d.ts +10 -0
- package/dist/mcp-tools/finance-reports.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-reports.mcp-tools.js +89 -0
- package/dist/mcp-tools/finance-reports.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-statements.mcp-tools.d.ts +34 -0
- package/dist/mcp-tools/finance-statements.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-statements.mcp-tools.js +253 -0
- package/dist/mcp-tools/finance-statements.mcp-tools.js.map +1 -0
- package/dist/mcp-tools/finance-transfers.mcp-tools.d.ts +9 -0
- package/dist/mcp-tools/finance-transfers.mcp-tools.d.ts.map +1 -0
- package/dist/mcp-tools/finance-transfers.mcp-tools.js +79 -0
- package/dist/mcp-tools/finance-transfers.mcp-tools.js.map +1 -0
- package/hedhog/data/route.yaml +659 -1
- package/hedhog/frontend/app/_components/finance-title-actions-menu.tsx.ejs +9 -3
- package/hedhog/frontend/app/_lib/http-error.ts.ejs +105 -0
- package/hedhog/frontend/app/_lib/use-finance-realtime-refresh.ts.ejs +62 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +3 -0
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +776 -344
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +7 -13
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +5 -5
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +802 -355
- package/hedhog/frontend/app/administration/categories/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/administration/cost-centers/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/administration/currencies/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/administration/period-close/page.tsx.ejs +9 -3
- package/hedhog/frontend/app/cash-and-banks/bank-accounts/page.tsx.ejs +7 -3
- package/hedhog/frontend/app/cash-and-banks/bank-reconciliation/page.tsx.ejs +3 -3
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +28 -7
- package/hedhog/frontend/app/cash-and-banks/transfers/page.tsx.ejs +12 -3
- package/hedhog/frontend/messages/en.json +63 -3
- package/hedhog/frontend/messages/pt.json +63 -3
- package/hedhog/table/financial_title.yaml +10 -0
- package/package.json +5 -5
- package/src/dto/create-financial-title.dto.ts +23 -0
- package/src/finance-realtime.controller.ts +12 -0
- package/src/finance-realtime.service.ts +106 -0
- package/src/finance.module.ts +28 -0
- package/src/finance.service.ts +184 -45
- package/src/mcp-tools/finance-audit-logs.mcp-tools.ts +38 -0
- package/src/mcp-tools/finance-bank-accounts.mcp-tools.ts +76 -0
- package/src/mcp-tools/finance-categories.mcp-tools.ts +86 -0
- package/src/mcp-tools/finance-collections.mcp-tools.ts +50 -0
- package/src/mcp-tools/finance-cost-centers.mcp-tools.ts +69 -0
- package/src/mcp-tools/finance-currencies.mcp-tools.ts +75 -0
- package/src/mcp-tools/finance-data.mcp-tools.ts +43 -0
- package/src/mcp-tools/finance-installments.mcp-tools.ts +560 -0
- package/src/mcp-tools/finance-period-close.mcp-tools.ts +53 -0
- package/src/mcp-tools/finance-reports.mcp-tools.ts +59 -0
- package/src/mcp-tools/finance-statements.mcp-tools.ts +202 -0
- package/src/mcp-tools/finance-transfers.mcp-tools.ts +53 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { PersonPickerField } from '@/app/(app)/(libraries)/
|
|
3
|
+
import { PersonPickerField } from '@/app/(app)/(libraries)/crm/_components/person-picker';
|
|
4
4
|
import { CategoryPickerField } from '@/app/(app)/(libraries)/finance/_components/category-picker-field';
|
|
5
5
|
import { CostCenterPickerField } from '@/app/(app)/(libraries)/finance/_components/cost-center-picker-field';
|
|
6
6
|
import {
|
|
@@ -40,6 +40,7 @@ import { KpiCardsGrid } from '@/components/ui/kpi-cards-grid';
|
|
|
40
40
|
import { Label } from '@/components/ui/label';
|
|
41
41
|
import { Money } from '@/components/ui/money';
|
|
42
42
|
import { Progress } from '@/components/ui/progress';
|
|
43
|
+
import { ResizableSheetContent } from '@/components/ui/resizable-sheet-content';
|
|
43
44
|
import {
|
|
44
45
|
Select,
|
|
45
46
|
SelectContent,
|
|
@@ -49,7 +50,6 @@ import {
|
|
|
49
50
|
} from '@/components/ui/select';
|
|
50
51
|
import {
|
|
51
52
|
Sheet,
|
|
52
|
-
SheetContent,
|
|
53
53
|
SheetDescription,
|
|
54
54
|
SheetHeader,
|
|
55
55
|
SheetTitle,
|
|
@@ -98,7 +98,12 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|
|
98
98
|
import { useFieldArray, useForm, useWatch } from 'react-hook-form';
|
|
99
99
|
import { z } from 'zod';
|
|
100
100
|
import { formatarData } from '../../_lib/formatters';
|
|
101
|
+
import {
|
|
102
|
+
getApiErrorMessage,
|
|
103
|
+
getFirstFormErrorMessage,
|
|
104
|
+
} from '../../_lib/http-error';
|
|
101
105
|
import { useFinanceData } from '../../_lib/use-finance-data';
|
|
106
|
+
import { useFinanceRealtimeRefresh } from '../../_lib/use-finance-realtime-refresh';
|
|
102
107
|
|
|
103
108
|
const INSTALLMENT_REDISTRIBUTION_DEBOUNCE_MS = 300;
|
|
104
109
|
|
|
@@ -200,6 +205,7 @@ const redistributeRemainingInstallments = (
|
|
|
200
205
|
const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
|
|
201
206
|
z
|
|
202
207
|
.object({
|
|
208
|
+
tipoTitulo: z.enum(['parcelado', 'recorrente']).default('parcelado'),
|
|
203
209
|
documento: z.string().trim().min(1, t('validation.documentRequired')),
|
|
204
210
|
clienteId: z.string().min(1, t('validation.clientRequired')),
|
|
205
211
|
competencia: z.string().optional(),
|
|
@@ -222,12 +228,37 @@ const getNewTitleFormSchema = (t: ReturnType<typeof useTranslations>) =>
|
|
|
222
228
|
})
|
|
223
229
|
)
|
|
224
230
|
.min(1, t('validation.installmentsRequired')),
|
|
231
|
+
recorrencia: z
|
|
232
|
+
.object({
|
|
233
|
+
frequencia: z.string().default('monthly'),
|
|
234
|
+
dataFim: z.string().optional(),
|
|
235
|
+
numOcorrencias: z.coerce
|
|
236
|
+
.number()
|
|
237
|
+
.int()
|
|
238
|
+
.min(1)
|
|
239
|
+
.max(600)
|
|
240
|
+
.optional()
|
|
241
|
+
.or(z.literal('').transform(() => undefined)),
|
|
242
|
+
})
|
|
243
|
+
.optional(),
|
|
225
244
|
categoriaId: z.string().optional(),
|
|
226
245
|
centroCustoId: z.string().optional(),
|
|
227
246
|
canal: z.string().optional(),
|
|
228
247
|
descricao: z.string().optional(),
|
|
229
248
|
})
|
|
230
249
|
.superRefine((values, ctx) => {
|
|
250
|
+
if (values.tipoTitulo === 'recorrente') {
|
|
251
|
+
const r = values.recorrencia;
|
|
252
|
+
if (!r?.dataFim && !r?.numOcorrencias) {
|
|
253
|
+
ctx.addIssue({
|
|
254
|
+
code: z.ZodIssueCode.custom,
|
|
255
|
+
path: ['recorrencia'],
|
|
256
|
+
message: t('validation.recurrenceEndOrCount'),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
231
262
|
if (values.installments.length !== values.installmentsCount) {
|
|
232
263
|
ctx.addIssue({
|
|
233
264
|
code: z.ZodIssueCode.custom,
|
|
@@ -274,16 +305,22 @@ function NovoTituloSheet({
|
|
|
274
305
|
t,
|
|
275
306
|
onCreated,
|
|
276
307
|
onOptionsUpdated,
|
|
308
|
+
open: controlledOpen,
|
|
309
|
+
onOpenChange,
|
|
277
310
|
}: {
|
|
278
311
|
categorias: any[];
|
|
279
312
|
centrosCusto: any[];
|
|
280
313
|
t: ReturnType<typeof useTranslations>;
|
|
281
314
|
onCreated: () => Promise<any> | void;
|
|
282
315
|
onOptionsUpdated?: () => Promise<any> | void;
|
|
316
|
+
open?: boolean;
|
|
317
|
+
onOpenChange?: (open: boolean) => void;
|
|
283
318
|
}) {
|
|
284
319
|
const { request, showToastHandler, currentLocaleCode, getSettingValue } =
|
|
285
320
|
useApp();
|
|
286
|
-
const [
|
|
321
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
322
|
+
const open = controlledOpen ?? internalOpen;
|
|
323
|
+
const setOpen = onOpenChange ?? setInternalOpen;
|
|
287
324
|
const [uploadedFileId, setUploadedFileId] = useState<number | null>(null);
|
|
288
325
|
const [uploadedFileName, setUploadedFileName] = useState('');
|
|
289
326
|
const [isUploadingFile, setIsUploadingFile] = useState(false);
|
|
@@ -324,6 +361,7 @@ function NovoTituloSheet({
|
|
|
324
361
|
const form = useForm<NewTitleFormValues>({
|
|
325
362
|
resolver: zodResolver(newTitleFormSchema),
|
|
326
363
|
defaultValues: {
|
|
364
|
+
tipoTitulo: 'parcelado',
|
|
327
365
|
documento: '',
|
|
328
366
|
clienteId: '',
|
|
329
367
|
competencia: '',
|
|
@@ -331,6 +369,11 @@ function NovoTituloSheet({
|
|
|
331
369
|
valor: 0,
|
|
332
370
|
installmentsCount: 1,
|
|
333
371
|
installments: [{ dueDate: '', amount: 0 }],
|
|
372
|
+
recorrencia: {
|
|
373
|
+
frequencia: 'monthly',
|
|
374
|
+
dataFim: '',
|
|
375
|
+
numOcorrencias: undefined,
|
|
376
|
+
},
|
|
334
377
|
categoriaId: '',
|
|
335
378
|
centroCustoId: '',
|
|
336
379
|
canal: '',
|
|
@@ -348,6 +391,9 @@ function NovoTituloSheet({
|
|
|
348
391
|
control: form.control,
|
|
349
392
|
});
|
|
350
393
|
|
|
394
|
+
const tipoTitulo = form.watch('tipoTitulo');
|
|
395
|
+
const isRecorrente = tipoTitulo === 'recorrente';
|
|
396
|
+
|
|
351
397
|
const {
|
|
352
398
|
clearDraft,
|
|
353
399
|
loadDraft,
|
|
@@ -359,6 +405,7 @@ function NovoTituloSheet({
|
|
|
359
405
|
mode: 'create',
|
|
360
406
|
titleId: null,
|
|
361
407
|
values: {
|
|
408
|
+
tipoTitulo: watchedFormValues.tipoTitulo ?? 'parcelado',
|
|
362
409
|
documento: watchedFormValues.documento ?? '',
|
|
363
410
|
clienteId: watchedFormValues.clienteId ?? '',
|
|
364
411
|
competencia: watchedFormValues.competencia ?? '',
|
|
@@ -369,6 +416,11 @@ function NovoTituloSheet({
|
|
|
369
416
|
dueDate: installment?.dueDate ?? '',
|
|
370
417
|
amount: Number(installment?.amount ?? 0),
|
|
371
418
|
})) ?? [{ dueDate: '', amount: 0 }],
|
|
419
|
+
recorrencia: {
|
|
420
|
+
frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
|
|
421
|
+
dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
|
|
422
|
+
numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
|
|
423
|
+
},
|
|
372
424
|
categoriaId: watchedFormValues.categoriaId ?? '',
|
|
373
425
|
centroCustoId: watchedFormValues.centroCustoId ?? '',
|
|
374
426
|
canal: watchedFormValues.canal ?? '',
|
|
@@ -422,7 +474,9 @@ function NovoTituloSheet({
|
|
|
422
474
|
currentLocaleCode
|
|
423
475
|
);
|
|
424
476
|
|
|
425
|
-
return t('draftStatus'
|
|
477
|
+
return t.has('draftStatus')
|
|
478
|
+
? t('draftStatus', { relativeLabel, absoluteLabel })
|
|
479
|
+
: `${relativeLabel} - ${absoluteLabel}`;
|
|
426
480
|
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft, t]);
|
|
427
481
|
|
|
428
482
|
const watchedInstallmentsCount = form.watch('installmentsCount');
|
|
@@ -454,6 +508,7 @@ function NovoTituloSheet({
|
|
|
454
508
|
}
|
|
455
509
|
|
|
456
510
|
form.reset({
|
|
511
|
+
tipoTitulo: 'parcelado',
|
|
457
512
|
documento: '',
|
|
458
513
|
clienteId: '',
|
|
459
514
|
competencia: '',
|
|
@@ -461,6 +516,11 @@ function NovoTituloSheet({
|
|
|
461
516
|
valor: 0,
|
|
462
517
|
installmentsCount: 1,
|
|
463
518
|
installments: [{ dueDate: '', amount: 0 }],
|
|
519
|
+
recorrencia: {
|
|
520
|
+
frequencia: 'monthly',
|
|
521
|
+
dataFim: '',
|
|
522
|
+
numOcorrencias: undefined,
|
|
523
|
+
},
|
|
464
524
|
categoriaId: '',
|
|
465
525
|
centroCustoId: '',
|
|
466
526
|
canal: '',
|
|
@@ -562,6 +622,7 @@ function NovoTituloSheet({
|
|
|
562
622
|
|
|
563
623
|
const handleSubmit = async (values: NewTitleFormValues) => {
|
|
564
624
|
try {
|
|
625
|
+
const recorrente = values.tipoTitulo === 'recorrente';
|
|
565
626
|
await request({
|
|
566
627
|
url: '/finance/accounts-receivable/installments',
|
|
567
628
|
method: 'POST',
|
|
@@ -581,11 +642,22 @@ function NovoTituloSheet({
|
|
|
581
642
|
: undefined,
|
|
582
643
|
payment_channel: values.canal || undefined,
|
|
583
644
|
description: values.descricao?.trim() || undefined,
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
645
|
+
...(recorrente
|
|
646
|
+
? {
|
|
647
|
+
recurrence_rule: {
|
|
648
|
+
frequency: values.recorrencia?.frequencia ?? 'monthly',
|
|
649
|
+
end_date: values.recorrencia?.dataFim || undefined,
|
|
650
|
+
max_occurrences:
|
|
651
|
+
values.recorrencia?.numOcorrencias || undefined,
|
|
652
|
+
},
|
|
653
|
+
}
|
|
654
|
+
: {
|
|
655
|
+
installments: values.installments.map((installment, index) => ({
|
|
656
|
+
installment_number: index + 1,
|
|
657
|
+
due_date: installment.dueDate || values.vencimento,
|
|
658
|
+
amount: installment.amount,
|
|
659
|
+
})),
|
|
660
|
+
}),
|
|
589
661
|
attachment_file_ids: uploadedFileId ? [uploadedFileId] : undefined,
|
|
590
662
|
},
|
|
591
663
|
});
|
|
@@ -601,8 +673,11 @@ function NovoTituloSheet({
|
|
|
601
673
|
setAutoRedistributeInstallments(true);
|
|
602
674
|
setOpen(false);
|
|
603
675
|
showToastHandler?.('success', t('messages.createSuccess'));
|
|
604
|
-
} catch {
|
|
605
|
-
showToastHandler?.(
|
|
676
|
+
} catch (error) {
|
|
677
|
+
showToastHandler?.(
|
|
678
|
+
'error',
|
|
679
|
+
getApiErrorMessage(error, t('messages.createError'))
|
|
680
|
+
);
|
|
606
681
|
}
|
|
607
682
|
};
|
|
608
683
|
|
|
@@ -610,6 +685,13 @@ function NovoTituloSheet({
|
|
|
610
685
|
setOpen(false);
|
|
611
686
|
};
|
|
612
687
|
|
|
688
|
+
const handleInvalidSubmit = (errors: unknown) => {
|
|
689
|
+
showToastHandler?.(
|
|
690
|
+
'error',
|
|
691
|
+
getFirstFormErrorMessage(errors, t('messages.createError'))
|
|
692
|
+
);
|
|
693
|
+
};
|
|
694
|
+
|
|
613
695
|
const clearUploadedFile = () => {
|
|
614
696
|
setUploadedFileId(null);
|
|
615
697
|
setUploadedFileName('');
|
|
@@ -781,7 +863,11 @@ function NovoTituloSheet({
|
|
|
781
863
|
{t('newTitle.action')}
|
|
782
864
|
</Button>
|
|
783
865
|
</SheetTrigger>
|
|
784
|
-
<
|
|
866
|
+
<ResizableSheetContent
|
|
867
|
+
sheetId="finance-receivable-create-title"
|
|
868
|
+
defaultWidth={896}
|
|
869
|
+
className="flex h-full w-full flex-col overflow-hidden p-0 gap-0"
|
|
870
|
+
>
|
|
785
871
|
<SheetHeader className="border-b border-border/50 px-4 py-4 sm:px-6">
|
|
786
872
|
<SheetTitle>{t('newTitle.title')}</SheetTitle>
|
|
787
873
|
<SheetDescription>{t('newTitle.description')}</SheetDescription>
|
|
@@ -789,7 +875,7 @@ function NovoTituloSheet({
|
|
|
789
875
|
<Form {...form}>
|
|
790
876
|
<form
|
|
791
877
|
className="flex h-full flex-col overflow-hidden"
|
|
792
|
-
onSubmit={form.handleSubmit(handleSubmit)}
|
|
878
|
+
onSubmit={form.handleSubmit(handleSubmit, handleInvalidSubmit)}
|
|
793
879
|
>
|
|
794
880
|
<FinanceSheetBody className="pt-0">
|
|
795
881
|
<FinanceSheetSection className="pt-0 mt-0">
|
|
@@ -947,6 +1033,39 @@ function NovoTituloSheet({
|
|
|
947
1033
|
selectPlaceholder={t('common.select')}
|
|
948
1034
|
/>
|
|
949
1035
|
|
|
1036
|
+
{/* Tipo de título: Parcelado / Recorrente */}
|
|
1037
|
+
<FormField
|
|
1038
|
+
control={form.control}
|
|
1039
|
+
name="tipoTitulo"
|
|
1040
|
+
render={({ field }) => (
|
|
1041
|
+
<FormItem>
|
|
1042
|
+
<FormLabel>{t('titleType.label')}</FormLabel>
|
|
1043
|
+
<div className="flex gap-2">
|
|
1044
|
+
<Button
|
|
1045
|
+
type="button"
|
|
1046
|
+
variant={
|
|
1047
|
+
field.value === 'parcelado' ? 'default' : 'outline'
|
|
1048
|
+
}
|
|
1049
|
+
size="sm"
|
|
1050
|
+
onClick={() => field.onChange('parcelado')}
|
|
1051
|
+
>
|
|
1052
|
+
{t('titleType.installment')}
|
|
1053
|
+
</Button>
|
|
1054
|
+
<Button
|
|
1055
|
+
type="button"
|
|
1056
|
+
variant={
|
|
1057
|
+
field.value === 'recorrente' ? 'default' : 'outline'
|
|
1058
|
+
}
|
|
1059
|
+
size="sm"
|
|
1060
|
+
onClick={() => field.onChange('recorrente')}
|
|
1061
|
+
>
|
|
1062
|
+
{t('titleType.recurring')}
|
|
1063
|
+
</Button>
|
|
1064
|
+
</div>
|
|
1065
|
+
</FormItem>
|
|
1066
|
+
)}
|
|
1067
|
+
/>
|
|
1068
|
+
|
|
950
1069
|
<div className="grid gap-4 xl:grid-cols-2">
|
|
951
1070
|
<div className="grid gap-4 md:grid-cols-2">
|
|
952
1071
|
<FormField
|
|
@@ -972,7 +1091,11 @@ function NovoTituloSheet({
|
|
|
972
1091
|
name="vencimento"
|
|
973
1092
|
render={({ field }) => (
|
|
974
1093
|
<FormItem>
|
|
975
|
-
<FormLabel>
|
|
1094
|
+
<FormLabel>
|
|
1095
|
+
{isRecorrente
|
|
1096
|
+
? t('fields.dueDate')
|
|
1097
|
+
: t('fields.dueDate')}
|
|
1098
|
+
</FormLabel>
|
|
976
1099
|
<FormControl>
|
|
977
1100
|
<Input
|
|
978
1101
|
type="date"
|
|
@@ -992,7 +1115,11 @@ function NovoTituloSheet({
|
|
|
992
1115
|
name="valor"
|
|
993
1116
|
render={({ field }) => (
|
|
994
1117
|
<FormItem>
|
|
995
|
-
<FormLabel>
|
|
1118
|
+
<FormLabel>
|
|
1119
|
+
{isRecorrente
|
|
1120
|
+
? t('fields.totalValueRecurring')
|
|
1121
|
+
: t('fields.totalValue')}
|
|
1122
|
+
</FormLabel>
|
|
996
1123
|
<FormControl>
|
|
997
1124
|
<InputMoney
|
|
998
1125
|
ref={field.ref}
|
|
@@ -1010,179 +1137,292 @@ function NovoTituloSheet({
|
|
|
1010
1137
|
)}
|
|
1011
1138
|
/>
|
|
1012
1139
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
<
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
<
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1140
|
+
{!isRecorrente && (
|
|
1141
|
+
<FormField
|
|
1142
|
+
control={form.control}
|
|
1143
|
+
name="installmentsCount"
|
|
1144
|
+
render={({ field }) => (
|
|
1145
|
+
<FormItem>
|
|
1146
|
+
<FormLabel>
|
|
1147
|
+
{t('installmentsEditor.countLabel')}
|
|
1148
|
+
</FormLabel>
|
|
1149
|
+
<FormControl>
|
|
1150
|
+
<Input
|
|
1151
|
+
type="number"
|
|
1152
|
+
min={1}
|
|
1153
|
+
max={120}
|
|
1154
|
+
value={field.value}
|
|
1155
|
+
onChange={(event) => {
|
|
1156
|
+
const nextValue = Number(
|
|
1157
|
+
event.target.value || 1
|
|
1158
|
+
);
|
|
1159
|
+
field.onChange(
|
|
1160
|
+
Number.isNaN(nextValue) ? 1 : nextValue
|
|
1161
|
+
);
|
|
1162
|
+
}}
|
|
1163
|
+
/>
|
|
1164
|
+
</FormControl>
|
|
1165
|
+
<FormMessage />
|
|
1166
|
+
</FormItem>
|
|
1167
|
+
)}
|
|
1168
|
+
/>
|
|
1169
|
+
)}
|
|
1041
1170
|
</div>
|
|
1042
1171
|
</div>
|
|
1043
1172
|
|
|
1044
|
-
|
|
1045
|
-
<div className="
|
|
1046
|
-
<
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
<div className="flex items-center gap-2">
|
|
1069
|
-
<Checkbox
|
|
1070
|
-
id="auto-redistribute-installments-receivable"
|
|
1071
|
-
checked={autoRedistributeInstallments}
|
|
1072
|
-
onCheckedChange={(checked) =>
|
|
1073
|
-
setAutoRedistributeInstallments(checked === true)
|
|
1074
|
-
}
|
|
1075
|
-
/>
|
|
1076
|
-
<Label
|
|
1077
|
-
htmlFor="auto-redistribute-installments-receivable"
|
|
1078
|
-
className="text-xs text-muted-foreground"
|
|
1079
|
-
>
|
|
1080
|
-
{t('installmentsEditor.autoRedistributeLabel')}
|
|
1081
|
-
</Label>
|
|
1082
|
-
</div>
|
|
1083
|
-
{autoRedistributeInstallments && (
|
|
1084
|
-
<p className="text-xs text-muted-foreground">
|
|
1085
|
-
{t('installmentsEditor.autoRedistributeHint')}
|
|
1086
|
-
</p>
|
|
1087
|
-
)}
|
|
1173
|
+
{!isRecorrente && (
|
|
1174
|
+
<div className="space-y-3 rounded-md border p-3">
|
|
1175
|
+
<div className="flex items-center justify-between gap-2">
|
|
1176
|
+
<p className="text-sm font-medium">
|
|
1177
|
+
{t('installmentsEditor.title')}
|
|
1178
|
+
</p>
|
|
1179
|
+
<Button
|
|
1180
|
+
type="button"
|
|
1181
|
+
variant="outline"
|
|
1182
|
+
size="sm"
|
|
1183
|
+
onClick={() => {
|
|
1184
|
+
setIsInstallmentsEdited(false);
|
|
1185
|
+
replaceInstallments(
|
|
1186
|
+
buildEqualInstallments(
|
|
1187
|
+
form.getValues('installmentsCount'),
|
|
1188
|
+
form.getValues('valor'),
|
|
1189
|
+
form.getValues('vencimento')
|
|
1190
|
+
)
|
|
1191
|
+
);
|
|
1192
|
+
}}
|
|
1193
|
+
>
|
|
1194
|
+
{t('installmentsEditor.recalculate')}
|
|
1195
|
+
</Button>
|
|
1196
|
+
</div>
|
|
1088
1197
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1198
|
+
<div className="flex items-center gap-2">
|
|
1199
|
+
<Checkbox
|
|
1200
|
+
id="auto-redistribute-installments-receivable"
|
|
1201
|
+
checked={autoRedistributeInstallments}
|
|
1202
|
+
onCheckedChange={(checked) =>
|
|
1203
|
+
setAutoRedistributeInstallments(checked === true)
|
|
1204
|
+
}
|
|
1205
|
+
/>
|
|
1206
|
+
<Label
|
|
1207
|
+
htmlFor="auto-redistribute-installments-receivable"
|
|
1208
|
+
className="text-xs text-muted-foreground"
|
|
1094
1209
|
>
|
|
1095
|
-
|
|
1096
|
-
|
|
1210
|
+
{t('installmentsEditor.autoRedistributeLabel')}
|
|
1211
|
+
</Label>
|
|
1212
|
+
</div>
|
|
1213
|
+
{autoRedistributeInstallments && (
|
|
1214
|
+
<p className="text-xs text-muted-foreground">
|
|
1215
|
+
{t('installmentsEditor.autoRedistributeHint')}
|
|
1216
|
+
</p>
|
|
1217
|
+
)}
|
|
1218
|
+
|
|
1219
|
+
<div className="space-y-2">
|
|
1220
|
+
{installmentFields.map((installment, index) => (
|
|
1221
|
+
<div
|
|
1222
|
+
key={installment.id}
|
|
1223
|
+
className="grid grid-cols-1 items-start gap-2 rounded-md border p-2 sm:grid-cols-[96px_1fr_160px]"
|
|
1224
|
+
>
|
|
1225
|
+
<div className="flex items-center text-sm text-muted-foreground">
|
|
1226
|
+
#{index + 1}
|
|
1227
|
+
</div>
|
|
1228
|
+
|
|
1229
|
+
<FormField
|
|
1230
|
+
control={form.control}
|
|
1231
|
+
name={`installments.${index}.dueDate` as const}
|
|
1232
|
+
render={({ field }) => (
|
|
1233
|
+
<FormItem>
|
|
1234
|
+
<FormLabel className="text-xs">
|
|
1235
|
+
{t('installmentsEditor.dueDateLabel')}
|
|
1236
|
+
</FormLabel>
|
|
1237
|
+
<FormControl>
|
|
1238
|
+
<Input
|
|
1239
|
+
type="date"
|
|
1240
|
+
{...field}
|
|
1241
|
+
value={field.value || ''}
|
|
1242
|
+
onChange={(event) => {
|
|
1243
|
+
setIsInstallmentsEdited(true);
|
|
1244
|
+
field.onChange(event);
|
|
1245
|
+
}}
|
|
1246
|
+
/>
|
|
1247
|
+
</FormControl>
|
|
1248
|
+
<FormMessage />
|
|
1249
|
+
</FormItem>
|
|
1250
|
+
)}
|
|
1251
|
+
/>
|
|
1252
|
+
|
|
1253
|
+
<FormField
|
|
1254
|
+
control={form.control}
|
|
1255
|
+
name={`installments.${index}.amount` as const}
|
|
1256
|
+
render={({ field }) => (
|
|
1257
|
+
<FormItem>
|
|
1258
|
+
<FormLabel className="text-xs">
|
|
1259
|
+
{t('installmentsEditor.amountLabel')}
|
|
1260
|
+
</FormLabel>
|
|
1261
|
+
<FormControl>
|
|
1262
|
+
<InputMoney
|
|
1263
|
+
ref={field.ref}
|
|
1264
|
+
name={field.name}
|
|
1265
|
+
value={field.value}
|
|
1266
|
+
onBlur={() => {
|
|
1267
|
+
field.onBlur();
|
|
1268
|
+
|
|
1269
|
+
if (!autoRedistributeInstallments) {
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
clearScheduledRedistribution(index);
|
|
1274
|
+
runInstallmentRedistribution(index);
|
|
1275
|
+
}}
|
|
1276
|
+
onValueChange={(value) => {
|
|
1277
|
+
setIsInstallmentsEdited(true);
|
|
1278
|
+
field.onChange(value ?? 0);
|
|
1279
|
+
|
|
1280
|
+
if (!autoRedistributeInstallments) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
scheduleInstallmentRedistribution(index);
|
|
1285
|
+
}}
|
|
1286
|
+
placeholder="0,00"
|
|
1287
|
+
/>
|
|
1288
|
+
</FormControl>
|
|
1289
|
+
<FormMessage />
|
|
1290
|
+
</FormItem>
|
|
1291
|
+
)}
|
|
1292
|
+
/>
|
|
1097
1293
|
</div>
|
|
1294
|
+
))}
|
|
1295
|
+
</div>
|
|
1098
1296
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
</FormItem>
|
|
1120
|
-
)}
|
|
1121
|
-
/>
|
|
1297
|
+
<p
|
|
1298
|
+
className={`text-xs ${
|
|
1299
|
+
installmentsDiffCents === 0
|
|
1300
|
+
? 'text-muted-foreground'
|
|
1301
|
+
: 'text-destructive'
|
|
1302
|
+
}`}
|
|
1303
|
+
>
|
|
1304
|
+
{t('installmentsEditor.totalPrefix', {
|
|
1305
|
+
total: installmentsTotal.toFixed(2),
|
|
1306
|
+
})}
|
|
1307
|
+
{installmentsDiffCents > 0 &&
|
|
1308
|
+
` ${t('installmentsEditor.adjustmentNeeded')}`}
|
|
1309
|
+
</p>
|
|
1310
|
+
{form.formState.errors.installments?.message && (
|
|
1311
|
+
<p className="text-xs text-destructive">
|
|
1312
|
+
{form.formState.errors.installments.message}
|
|
1313
|
+
</p>
|
|
1314
|
+
)}
|
|
1315
|
+
</div>
|
|
1316
|
+
)}
|
|
1122
1317
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1318
|
+
{isRecorrente && (
|
|
1319
|
+
<div className="space-y-3 rounded-md border p-3">
|
|
1320
|
+
<p className="text-sm font-medium">
|
|
1321
|
+
{t('recurrenceEditor.title')}
|
|
1322
|
+
</p>
|
|
1323
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
1324
|
+
<FormField
|
|
1325
|
+
control={form.control}
|
|
1326
|
+
name="recorrencia.frequencia"
|
|
1327
|
+
render={({ field }) => (
|
|
1328
|
+
<FormItem>
|
|
1329
|
+
<FormLabel>
|
|
1330
|
+
{t('recurrenceEditor.frequencyLabel')}
|
|
1331
|
+
</FormLabel>
|
|
1332
|
+
<Select
|
|
1333
|
+
value={field.value}
|
|
1334
|
+
onValueChange={field.onChange}
|
|
1335
|
+
>
|
|
1131
1336
|
<FormControl>
|
|
1132
|
-
<
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
value={field.value}
|
|
1136
|
-
onBlur={() => {
|
|
1137
|
-
field.onBlur();
|
|
1138
|
-
|
|
1139
|
-
if (!autoRedistributeInstallments) {
|
|
1140
|
-
return;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
clearScheduledRedistribution(index);
|
|
1144
|
-
runInstallmentRedistribution(index);
|
|
1145
|
-
}}
|
|
1146
|
-
onValueChange={(value) => {
|
|
1147
|
-
setIsInstallmentsEdited(true);
|
|
1148
|
-
field.onChange(value ?? 0);
|
|
1149
|
-
|
|
1150
|
-
if (!autoRedistributeInstallments) {
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
scheduleInstallmentRedistribution(index);
|
|
1155
|
-
}}
|
|
1156
|
-
placeholder="0,00"
|
|
1157
|
-
/>
|
|
1337
|
+
<SelectTrigger className="w-full">
|
|
1338
|
+
<SelectValue />
|
|
1339
|
+
</SelectTrigger>
|
|
1158
1340
|
</FormControl>
|
|
1159
|
-
<
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1341
|
+
<SelectContent>
|
|
1342
|
+
<SelectItem value="weekly">
|
|
1343
|
+
{t('recurrenceEditor.frequencies.weekly')}
|
|
1344
|
+
</SelectItem>
|
|
1345
|
+
<SelectItem value="biweekly">
|
|
1346
|
+
{t('recurrenceEditor.frequencies.biweekly')}
|
|
1347
|
+
</SelectItem>
|
|
1348
|
+
<SelectItem value="monthly">
|
|
1349
|
+
{t('recurrenceEditor.frequencies.monthly')}
|
|
1350
|
+
</SelectItem>
|
|
1351
|
+
<SelectItem value="bimonthly">
|
|
1352
|
+
{t('recurrenceEditor.frequencies.bimonthly')}
|
|
1353
|
+
</SelectItem>
|
|
1354
|
+
<SelectItem value="quarterly">
|
|
1355
|
+
{t('recurrenceEditor.frequencies.quarterly')}
|
|
1356
|
+
</SelectItem>
|
|
1357
|
+
<SelectItem value="semiannual">
|
|
1358
|
+
{t('recurrenceEditor.frequencies.semiannual')}
|
|
1359
|
+
</SelectItem>
|
|
1360
|
+
<SelectItem value="annual">
|
|
1361
|
+
{t('recurrenceEditor.frequencies.annual')}
|
|
1362
|
+
</SelectItem>
|
|
1363
|
+
</SelectContent>
|
|
1364
|
+
</Select>
|
|
1365
|
+
<FormMessage />
|
|
1366
|
+
</FormItem>
|
|
1367
|
+
)}
|
|
1368
|
+
/>
|
|
1369
|
+
|
|
1370
|
+
<FormField
|
|
1371
|
+
control={form.control}
|
|
1372
|
+
name="recorrencia.dataFim"
|
|
1373
|
+
render={({ field }) => (
|
|
1374
|
+
<FormItem>
|
|
1375
|
+
<FormLabel>
|
|
1376
|
+
{t('recurrenceEditor.endDateLabel')}
|
|
1377
|
+
</FormLabel>
|
|
1378
|
+
<FormControl>
|
|
1379
|
+
<Input
|
|
1380
|
+
type="date"
|
|
1381
|
+
{...field}
|
|
1382
|
+
value={field.value || ''}
|
|
1383
|
+
/>
|
|
1384
|
+
</FormControl>
|
|
1385
|
+
<FormMessage />
|
|
1386
|
+
</FormItem>
|
|
1387
|
+
)}
|
|
1388
|
+
/>
|
|
1389
|
+
|
|
1390
|
+
<FormField
|
|
1391
|
+
control={form.control}
|
|
1392
|
+
name="recorrencia.numOcorrencias"
|
|
1393
|
+
render={({ field }) => (
|
|
1394
|
+
<FormItem>
|
|
1395
|
+
<FormLabel>
|
|
1396
|
+
{t('recurrenceEditor.maxOccurrencesLabel')}
|
|
1397
|
+
</FormLabel>
|
|
1398
|
+
<FormControl>
|
|
1399
|
+
<Input
|
|
1400
|
+
type="number"
|
|
1401
|
+
min={1}
|
|
1402
|
+
max={600}
|
|
1403
|
+
{...field}
|
|
1404
|
+
value={field.value ?? ''}
|
|
1405
|
+
onChange={(e) =>
|
|
1406
|
+
field.onChange(
|
|
1407
|
+
e.target.value === ''
|
|
1408
|
+
? undefined
|
|
1409
|
+
: Number(e.target.value)
|
|
1410
|
+
)
|
|
1411
|
+
}
|
|
1412
|
+
/>
|
|
1413
|
+
</FormControl>
|
|
1414
|
+
<FormMessage />
|
|
1415
|
+
</FormItem>
|
|
1416
|
+
)}
|
|
1417
|
+
/>
|
|
1418
|
+
</div>
|
|
1419
|
+
{form.formState.errors.recorrencia?.message && (
|
|
1420
|
+
<p className="text-xs text-destructive">
|
|
1421
|
+
{form.formState.errors.recorrencia.message}
|
|
1422
|
+
</p>
|
|
1423
|
+
)}
|
|
1165
1424
|
</div>
|
|
1166
|
-
|
|
1167
|
-
<p
|
|
1168
|
-
className={`text-xs ${
|
|
1169
|
-
installmentsDiffCents === 0
|
|
1170
|
-
? 'text-muted-foreground'
|
|
1171
|
-
: 'text-destructive'
|
|
1172
|
-
}`}
|
|
1173
|
-
>
|
|
1174
|
-
{t('installmentsEditor.totalPrefix', {
|
|
1175
|
-
total: installmentsTotal.toFixed(2),
|
|
1176
|
-
})}
|
|
1177
|
-
{installmentsDiffCents > 0 &&
|
|
1178
|
-
` ${t('installmentsEditor.adjustmentNeeded')}`}
|
|
1179
|
-
</p>
|
|
1180
|
-
{form.formState.errors.installments?.message && (
|
|
1181
|
-
<p className="text-xs text-destructive">
|
|
1182
|
-
{form.formState.errors.installments.message}
|
|
1183
|
-
</p>
|
|
1184
|
-
)}
|
|
1185
|
-
</div>
|
|
1425
|
+
)}
|
|
1186
1426
|
</FinanceSheetSection>
|
|
1187
1427
|
|
|
1188
1428
|
<FinanceSheetSection
|
|
@@ -1305,7 +1545,7 @@ function NovoTituloSheet({
|
|
|
1305
1545
|
</div>
|
|
1306
1546
|
</form>
|
|
1307
1547
|
</Form>
|
|
1308
|
-
</
|
|
1548
|
+
</ResizableSheetContent>
|
|
1309
1549
|
</Sheet>
|
|
1310
1550
|
);
|
|
1311
1551
|
}
|
|
@@ -1371,6 +1611,7 @@ function EditarTituloSheet({
|
|
|
1371
1611
|
const form = useForm<NewTitleFormValues>({
|
|
1372
1612
|
resolver: zodResolver(newTitleFormSchema),
|
|
1373
1613
|
defaultValues: {
|
|
1614
|
+
tipoTitulo: 'parcelado',
|
|
1374
1615
|
documento: '',
|
|
1375
1616
|
clienteId: '',
|
|
1376
1617
|
competencia: '',
|
|
@@ -1378,6 +1619,11 @@ function EditarTituloSheet({
|
|
|
1378
1619
|
valor: 0,
|
|
1379
1620
|
installmentsCount: 1,
|
|
1380
1621
|
installments: [{ dueDate: '', amount: 0 }],
|
|
1622
|
+
recorrencia: {
|
|
1623
|
+
frequencia: 'monthly',
|
|
1624
|
+
dataFim: '',
|
|
1625
|
+
numOcorrencias: undefined,
|
|
1626
|
+
},
|
|
1381
1627
|
categoriaId: '',
|
|
1382
1628
|
centroCustoId: '',
|
|
1383
1629
|
canal: '',
|
|
@@ -1395,6 +1641,9 @@ function EditarTituloSheet({
|
|
|
1395
1641
|
control: form.control,
|
|
1396
1642
|
});
|
|
1397
1643
|
|
|
1644
|
+
const tipoTituloEdit = form.watch('tipoTitulo');
|
|
1645
|
+
const isRecorrenteEdit = tipoTituloEdit === 'recorrente';
|
|
1646
|
+
|
|
1398
1647
|
const {
|
|
1399
1648
|
clearDraft,
|
|
1400
1649
|
loadDraft,
|
|
@@ -1406,6 +1655,7 @@ function EditarTituloSheet({
|
|
|
1406
1655
|
mode: 'edit',
|
|
1407
1656
|
titleId: titulo?.id ? String(titulo.id) : null,
|
|
1408
1657
|
values: {
|
|
1658
|
+
tipoTitulo: watchedFormValues.tipoTitulo ?? 'parcelado',
|
|
1409
1659
|
documento: watchedFormValues.documento ?? '',
|
|
1410
1660
|
clienteId: watchedFormValues.clienteId ?? '',
|
|
1411
1661
|
competencia: watchedFormValues.competencia ?? '',
|
|
@@ -1416,6 +1666,11 @@ function EditarTituloSheet({
|
|
|
1416
1666
|
dueDate: installment?.dueDate ?? '',
|
|
1417
1667
|
amount: Number(installment?.amount ?? 0),
|
|
1418
1668
|
})) ?? [{ dueDate: '', amount: 0 }],
|
|
1669
|
+
recorrencia: {
|
|
1670
|
+
frequencia: watchedFormValues.recorrencia?.frequencia ?? 'monthly',
|
|
1671
|
+
dataFim: watchedFormValues.recorrencia?.dataFim ?? '',
|
|
1672
|
+
numOcorrencias: watchedFormValues.recorrencia?.numOcorrencias,
|
|
1673
|
+
},
|
|
1419
1674
|
categoriaId: watchedFormValues.categoriaId ?? '',
|
|
1420
1675
|
centroCustoId: watchedFormValues.centroCustoId ?? '',
|
|
1421
1676
|
canal: watchedFormValues.canal ?? '',
|
|
@@ -1469,7 +1724,9 @@ function EditarTituloSheet({
|
|
|
1469
1724
|
currentLocaleCode
|
|
1470
1725
|
);
|
|
1471
1726
|
|
|
1472
|
-
return t('draftStatus'
|
|
1727
|
+
return t.has('draftStatus')
|
|
1728
|
+
? t('draftStatus', { relativeLabel, absoluteLabel })
|
|
1729
|
+
: `${relativeLabel} - ${absoluteLabel}`;
|
|
1473
1730
|
}, [draftSavedAt, currentLocaleCode, getSettingValue, hasDraft, t]);
|
|
1474
1731
|
|
|
1475
1732
|
const watchedInstallmentsCount = form.watch('installmentsCount');
|
|
@@ -1532,15 +1789,24 @@ function EditarTituloSheet({
|
|
|
1532
1789
|
},
|
|
1533
1790
|
];
|
|
1534
1791
|
|
|
1792
|
+
const isRecurringTitle = Boolean(titulo.isRecurring);
|
|
1535
1793
|
form.reset({
|
|
1794
|
+
tipoTitulo: isRecurringTitle ? 'recorrente' : 'parcelado',
|
|
1536
1795
|
documento: titulo.documento || '',
|
|
1537
1796
|
clienteId: titulo.clienteId || '',
|
|
1538
1797
|
competencia: titulo.competencia || '',
|
|
1539
1798
|
vencimento:
|
|
1540
1799
|
normalizedInstallments[0]?.dueDate || toDateInput(titulo?.vencimento),
|
|
1541
|
-
valor:
|
|
1800
|
+
valor: isRecurringTitle
|
|
1801
|
+
? Number(normalizedInstallments[0]?.amount || 0)
|
|
1802
|
+
: Number(titulo.valorTotal || 0),
|
|
1542
1803
|
installmentsCount: normalizedInstallments.length,
|
|
1543
1804
|
installments: normalizedInstallments,
|
|
1805
|
+
recorrencia: {
|
|
1806
|
+
frequencia: titulo.recurrenceFrequency || 'monthly',
|
|
1807
|
+
dataFim: titulo.recurrenceEndDate || '',
|
|
1808
|
+
numOcorrencias: undefined,
|
|
1809
|
+
},
|
|
1544
1810
|
categoriaId: titulo.categoriaId || '',
|
|
1545
1811
|
centroCustoId: titulo.centroCustoId || '',
|
|
1546
1812
|
canal: installments[0]?.canal || '',
|
|
@@ -1839,6 +2105,7 @@ function EditarTituloSheet({
|
|
|
1839
2105
|
}
|
|
1840
2106
|
|
|
1841
2107
|
try {
|
|
2108
|
+
const recorrente = values.tipoTitulo === 'recorrente';
|
|
1842
2109
|
await request({
|
|
1843
2110
|
url: `/finance/accounts-receivable/installments/${titulo.id}`,
|
|
1844
2111
|
method: 'PATCH',
|
|
@@ -1858,11 +2125,22 @@ function EditarTituloSheet({
|
|
|
1858
2125
|
: undefined,
|
|
1859
2126
|
payment_channel: values.canal || undefined,
|
|
1860
2127
|
description: values.descricao?.trim() || undefined,
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2128
|
+
...(recorrente
|
|
2129
|
+
? {
|
|
2130
|
+
recurrence_rule: {
|
|
2131
|
+
frequency: values.recorrencia?.frequencia ?? 'monthly',
|
|
2132
|
+
end_date: values.recorrencia?.dataFim || undefined,
|
|
2133
|
+
max_occurrences:
|
|
2134
|
+
values.recorrencia?.numOcorrencias || undefined,
|
|
2135
|
+
},
|
|
2136
|
+
}
|
|
2137
|
+
: {
|
|
2138
|
+
installments: values.installments.map((installment, index) => ({
|
|
2139
|
+
installment_number: index + 1,
|
|
2140
|
+
due_date: installment.dueDate || values.vencimento,
|
|
2141
|
+
amount: installment.amount,
|
|
2142
|
+
})),
|
|
2143
|
+
}),
|
|
1866
2144
|
attachment_file_ids: uploadedFileId ? [uploadedFileId] : undefined,
|
|
1867
2145
|
},
|
|
1868
2146
|
});
|
|
@@ -1871,8 +2149,11 @@ function EditarTituloSheet({
|
|
|
1871
2149
|
await onUpdated();
|
|
1872
2150
|
showToastHandler?.('success', t('messages.updateSuccess'));
|
|
1873
2151
|
onOpenChange(false);
|
|
1874
|
-
} catch {
|
|
1875
|
-
showToastHandler?.(
|
|
2152
|
+
} catch (error) {
|
|
2153
|
+
showToastHandler?.(
|
|
2154
|
+
'error',
|
|
2155
|
+
getApiErrorMessage(error, t('messages.updateError'))
|
|
2156
|
+
);
|
|
1876
2157
|
}
|
|
1877
2158
|
};
|
|
1878
2159
|
|
|
@@ -1880,9 +2161,20 @@ function EditarTituloSheet({
|
|
|
1880
2161
|
onOpenChange(false);
|
|
1881
2162
|
};
|
|
1882
2163
|
|
|
2164
|
+
const handleInvalidSubmit = (errors: unknown) => {
|
|
2165
|
+
showToastHandler?.(
|
|
2166
|
+
'error',
|
|
2167
|
+
getFirstFormErrorMessage(errors, t('messages.updateError'))
|
|
2168
|
+
);
|
|
2169
|
+
};
|
|
2170
|
+
|
|
1883
2171
|
return (
|
|
1884
2172
|
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
1885
|
-
<
|
|
2173
|
+
<ResizableSheetContent
|
|
2174
|
+
sheetId="finance-receivable-edit-title"
|
|
2175
|
+
defaultWidth={896}
|
|
2176
|
+
className="flex h-full w-full flex-col overflow-hidden p-0"
|
|
2177
|
+
>
|
|
1886
2178
|
<SheetHeader className="border-b border-border/50 px-4 py-4 sm:px-6">
|
|
1887
2179
|
<SheetTitle>{t('table.actions.edit')}</SheetTitle>
|
|
1888
2180
|
<SheetDescription>{t('editTitle.description')}</SheetDescription>
|
|
@@ -1890,7 +2182,7 @@ function EditarTituloSheet({
|
|
|
1890
2182
|
<Form {...form}>
|
|
1891
2183
|
<form
|
|
1892
2184
|
className="flex h-full flex-col overflow-hidden"
|
|
1893
|
-
onSubmit={form.handleSubmit(handleSubmit)}
|
|
2185
|
+
onSubmit={form.handleSubmit(handleSubmit, handleInvalidSubmit)}
|
|
1894
2186
|
>
|
|
1895
2187
|
<FinanceSheetBody>
|
|
1896
2188
|
<FinanceSheetSection
|
|
@@ -2051,6 +2343,39 @@ function EditarTituloSheet({
|
|
|
2051
2343
|
selectPlaceholder={t('common.select')}
|
|
2052
2344
|
/>
|
|
2053
2345
|
|
|
2346
|
+
{/* Tipo de título: Parcelado / Recorrente */}
|
|
2347
|
+
<FormField
|
|
2348
|
+
control={form.control}
|
|
2349
|
+
name="tipoTitulo"
|
|
2350
|
+
render={({ field }) => (
|
|
2351
|
+
<FormItem>
|
|
2352
|
+
<FormLabel>{t('titleType.label')}</FormLabel>
|
|
2353
|
+
<div className="flex gap-2">
|
|
2354
|
+
<Button
|
|
2355
|
+
type="button"
|
|
2356
|
+
variant={
|
|
2357
|
+
field.value === 'parcelado' ? 'default' : 'outline'
|
|
2358
|
+
}
|
|
2359
|
+
size="sm"
|
|
2360
|
+
onClick={() => field.onChange('parcelado')}
|
|
2361
|
+
>
|
|
2362
|
+
{t('titleType.installment')}
|
|
2363
|
+
</Button>
|
|
2364
|
+
<Button
|
|
2365
|
+
type="button"
|
|
2366
|
+
variant={
|
|
2367
|
+
field.value === 'recorrente' ? 'default' : 'outline'
|
|
2368
|
+
}
|
|
2369
|
+
size="sm"
|
|
2370
|
+
onClick={() => field.onChange('recorrente')}
|
|
2371
|
+
>
|
|
2372
|
+
{t('titleType.recurring')}
|
|
2373
|
+
</Button>
|
|
2374
|
+
</div>
|
|
2375
|
+
</FormItem>
|
|
2376
|
+
)}
|
|
2377
|
+
/>
|
|
2378
|
+
|
|
2054
2379
|
<div className="grid gap-4 xl:grid-cols-2">
|
|
2055
2380
|
<div className="grid gap-4 md:grid-cols-2">
|
|
2056
2381
|
<FormField
|
|
@@ -2096,7 +2421,11 @@ function EditarTituloSheet({
|
|
|
2096
2421
|
name="valor"
|
|
2097
2422
|
render={({ field }) => (
|
|
2098
2423
|
<FormItem>
|
|
2099
|
-
<FormLabel>
|
|
2424
|
+
<FormLabel>
|
|
2425
|
+
{isRecorrenteEdit
|
|
2426
|
+
? t('fields.totalValueRecurring')
|
|
2427
|
+
: t('fields.totalValue')}
|
|
2428
|
+
</FormLabel>
|
|
2100
2429
|
<FormControl>
|
|
2101
2430
|
<InputMoney
|
|
2102
2431
|
ref={field.ref}
|
|
@@ -2114,181 +2443,294 @@ function EditarTituloSheet({
|
|
|
2114
2443
|
)}
|
|
2115
2444
|
/>
|
|
2116
2445
|
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
<
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
<
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2446
|
+
{!isRecorrenteEdit && (
|
|
2447
|
+
<FormField
|
|
2448
|
+
control={form.control}
|
|
2449
|
+
name="installmentsCount"
|
|
2450
|
+
render={({ field }) => (
|
|
2451
|
+
<FormItem>
|
|
2452
|
+
<FormLabel>
|
|
2453
|
+
{t('installmentsEditor.countLabel')}
|
|
2454
|
+
</FormLabel>
|
|
2455
|
+
<FormControl>
|
|
2456
|
+
<Input
|
|
2457
|
+
type="number"
|
|
2458
|
+
min={1}
|
|
2459
|
+
max={120}
|
|
2460
|
+
value={field.value}
|
|
2461
|
+
onChange={(event) => {
|
|
2462
|
+
const nextValue = Number(
|
|
2463
|
+
event.target.value || 1
|
|
2464
|
+
);
|
|
2465
|
+
field.onChange(
|
|
2466
|
+
Number.isNaN(nextValue) ? 1 : nextValue
|
|
2467
|
+
);
|
|
2468
|
+
setIsInstallmentsEdited(false);
|
|
2469
|
+
}}
|
|
2470
|
+
/>
|
|
2471
|
+
</FormControl>
|
|
2472
|
+
<FormMessage />
|
|
2473
|
+
</FormItem>
|
|
2474
|
+
)}
|
|
2475
|
+
/>
|
|
2476
|
+
)}
|
|
2146
2477
|
</div>
|
|
2147
2478
|
</div>
|
|
2148
2479
|
|
|
2149
|
-
|
|
2150
|
-
<div className="
|
|
2151
|
-
<
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2480
|
+
{!isRecorrenteEdit && (
|
|
2481
|
+
<div className="space-y-3 rounded-md border p-3">
|
|
2482
|
+
<div className="flex items-center justify-between gap-2">
|
|
2483
|
+
<p className="text-sm font-medium">
|
|
2484
|
+
{t('installmentsEditor.title')}
|
|
2485
|
+
</p>
|
|
2486
|
+
<Button
|
|
2487
|
+
type="button"
|
|
2488
|
+
variant="outline"
|
|
2489
|
+
size="sm"
|
|
2490
|
+
onClick={() => {
|
|
2491
|
+
setIsInstallmentsEdited(false);
|
|
2492
|
+
replaceInstallments(
|
|
2493
|
+
buildEqualInstallments(
|
|
2494
|
+
form.getValues('installmentsCount'),
|
|
2495
|
+
form.getValues('valor'),
|
|
2496
|
+
form.getValues('vencimento')
|
|
2497
|
+
)
|
|
2498
|
+
);
|
|
2499
|
+
}}
|
|
2500
|
+
>
|
|
2501
|
+
{t('installmentsEditor.recalculate')}
|
|
2502
|
+
</Button>
|
|
2503
|
+
</div>
|
|
2172
2504
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2505
|
+
<div className="flex items-center gap-2">
|
|
2506
|
+
<Checkbox
|
|
2507
|
+
id="auto-redistribute-installments-edit-receivable"
|
|
2508
|
+
checked={autoRedistributeInstallments}
|
|
2509
|
+
onCheckedChange={(checked) =>
|
|
2510
|
+
setAutoRedistributeInstallments(checked === true)
|
|
2511
|
+
}
|
|
2512
|
+
/>
|
|
2513
|
+
<Label
|
|
2514
|
+
htmlFor="auto-redistribute-installments-edit-receivable"
|
|
2515
|
+
className="text-xs text-muted-foreground"
|
|
2516
|
+
>
|
|
2517
|
+
{t('installmentsEditor.autoRedistributeLabel')}
|
|
2518
|
+
</Label>
|
|
2519
|
+
</div>
|
|
2188
2520
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2521
|
+
{autoRedistributeInstallments && (
|
|
2522
|
+
<p className="text-xs text-muted-foreground">
|
|
2523
|
+
{t('installmentsEditor.autoRedistributeHint')}
|
|
2524
|
+
</p>
|
|
2525
|
+
)}
|
|
2194
2526
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2527
|
+
<div className="space-y-2">
|
|
2528
|
+
{installmentFields.map((installment, index) => (
|
|
2529
|
+
<div
|
|
2530
|
+
key={installment.id}
|
|
2531
|
+
className="grid grid-cols-1 items-start gap-2 rounded-md border p-2 sm:grid-cols-[96px_1fr_160px]"
|
|
2532
|
+
>
|
|
2533
|
+
<div className="flex items-center text-sm text-muted-foreground">
|
|
2534
|
+
#{index + 1}
|
|
2535
|
+
</div>
|
|
2536
|
+
|
|
2537
|
+
<FormField
|
|
2538
|
+
control={form.control}
|
|
2539
|
+
name={`installments.${index}.dueDate` as const}
|
|
2540
|
+
render={({ field }) => (
|
|
2541
|
+
<FormItem>
|
|
2542
|
+
<FormLabel className="text-xs">
|
|
2543
|
+
{t('installmentsEditor.dueDateLabel')}
|
|
2544
|
+
</FormLabel>
|
|
2545
|
+
<FormControl>
|
|
2546
|
+
<Input
|
|
2547
|
+
type="date"
|
|
2548
|
+
{...field}
|
|
2549
|
+
value={field.value || ''}
|
|
2550
|
+
onChange={(event) => {
|
|
2551
|
+
setIsInstallmentsEdited(true);
|
|
2552
|
+
field.onChange(event);
|
|
2553
|
+
}}
|
|
2554
|
+
/>
|
|
2555
|
+
</FormControl>
|
|
2556
|
+
<FormMessage />
|
|
2557
|
+
</FormItem>
|
|
2558
|
+
)}
|
|
2559
|
+
/>
|
|
2560
|
+
|
|
2561
|
+
<FormField
|
|
2562
|
+
control={form.control}
|
|
2563
|
+
name={`installments.${index}.amount` as const}
|
|
2564
|
+
render={({ field }) => (
|
|
2565
|
+
<FormItem>
|
|
2566
|
+
<FormLabel className="text-xs">
|
|
2567
|
+
{t('installmentsEditor.amountLabel')}
|
|
2568
|
+
</FormLabel>
|
|
2569
|
+
<FormControl>
|
|
2570
|
+
<InputMoney
|
|
2571
|
+
ref={field.ref}
|
|
2572
|
+
name={field.name}
|
|
2573
|
+
value={field.value}
|
|
2574
|
+
onBlur={() => {
|
|
2575
|
+
field.onBlur();
|
|
2576
|
+
|
|
2577
|
+
if (!autoRedistributeInstallments) {
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
clearScheduledRedistribution(index);
|
|
2582
|
+
runInstallmentRedistribution(index);
|
|
2583
|
+
}}
|
|
2584
|
+
onValueChange={(value) => {
|
|
2585
|
+
setIsInstallmentsEdited(true);
|
|
2586
|
+
field.onChange(value ?? 0);
|
|
2587
|
+
|
|
2588
|
+
if (!autoRedistributeInstallments) {
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
scheduleInstallmentRedistribution(index);
|
|
2593
|
+
}}
|
|
2594
|
+
placeholder="0,00"
|
|
2595
|
+
/>
|
|
2596
|
+
</FormControl>
|
|
2597
|
+
<FormMessage />
|
|
2598
|
+
</FormItem>
|
|
2599
|
+
)}
|
|
2600
|
+
/>
|
|
2203
2601
|
</div>
|
|
2602
|
+
))}
|
|
2603
|
+
</div>
|
|
2204
2604
|
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
</FormItem>
|
|
2226
|
-
)}
|
|
2227
|
-
/>
|
|
2605
|
+
<p
|
|
2606
|
+
className={`text-xs ${
|
|
2607
|
+
installmentsDiffCents === 0
|
|
2608
|
+
? 'text-muted-foreground'
|
|
2609
|
+
: 'text-destructive'
|
|
2610
|
+
}`}
|
|
2611
|
+
>
|
|
2612
|
+
{t('installmentsEditor.totalPrefix', {
|
|
2613
|
+
total: installmentsTotal.toFixed(2),
|
|
2614
|
+
})}
|
|
2615
|
+
{installmentsDiffCents > 0 &&
|
|
2616
|
+
` ${t('installmentsEditor.adjustmentNeeded')}`}
|
|
2617
|
+
</p>
|
|
2618
|
+
{form.formState.errors.installments?.message && (
|
|
2619
|
+
<p className="text-xs text-destructive">
|
|
2620
|
+
{form.formState.errors.installments.message}
|
|
2621
|
+
</p>
|
|
2622
|
+
)}
|
|
2623
|
+
</div>
|
|
2624
|
+
)}
|
|
2228
2625
|
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2626
|
+
{isRecorrenteEdit && (
|
|
2627
|
+
<div className="space-y-3 rounded-md border p-3">
|
|
2628
|
+
<p className="text-sm font-medium">
|
|
2629
|
+
{t('recurrenceEditor.title')}
|
|
2630
|
+
</p>
|
|
2631
|
+
<div className="grid gap-4 md:grid-cols-3">
|
|
2632
|
+
<FormField
|
|
2633
|
+
control={form.control}
|
|
2634
|
+
name="recorrencia.frequencia"
|
|
2635
|
+
render={({ field }) => (
|
|
2636
|
+
<FormItem>
|
|
2637
|
+
<FormLabel>
|
|
2638
|
+
{t('recurrenceEditor.frequencyLabel')}
|
|
2639
|
+
</FormLabel>
|
|
2640
|
+
<Select
|
|
2641
|
+
value={field.value}
|
|
2642
|
+
onValueChange={field.onChange}
|
|
2643
|
+
>
|
|
2237
2644
|
<FormControl>
|
|
2238
|
-
<
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
value={field.value}
|
|
2242
|
-
onBlur={() => {
|
|
2243
|
-
field.onBlur();
|
|
2244
|
-
|
|
2245
|
-
if (!autoRedistributeInstallments) {
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
clearScheduledRedistribution(index);
|
|
2250
|
-
runInstallmentRedistribution(index);
|
|
2251
|
-
}}
|
|
2252
|
-
onValueChange={(value) => {
|
|
2253
|
-
setIsInstallmentsEdited(true);
|
|
2254
|
-
field.onChange(value ?? 0);
|
|
2255
|
-
|
|
2256
|
-
if (!autoRedistributeInstallments) {
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
|
|
2260
|
-
scheduleInstallmentRedistribution(index);
|
|
2261
|
-
}}
|
|
2262
|
-
placeholder="0,00"
|
|
2263
|
-
/>
|
|
2645
|
+
<SelectTrigger className="w-full">
|
|
2646
|
+
<SelectValue />
|
|
2647
|
+
</SelectTrigger>
|
|
2264
2648
|
</FormControl>
|
|
2265
|
-
<
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2649
|
+
<SelectContent>
|
|
2650
|
+
<SelectItem value="weekly">
|
|
2651
|
+
{t('recurrenceEditor.frequencies.weekly')}
|
|
2652
|
+
</SelectItem>
|
|
2653
|
+
<SelectItem value="biweekly">
|
|
2654
|
+
{t('recurrenceEditor.frequencies.biweekly')}
|
|
2655
|
+
</SelectItem>
|
|
2656
|
+
<SelectItem value="monthly">
|
|
2657
|
+
{t('recurrenceEditor.frequencies.monthly')}
|
|
2658
|
+
</SelectItem>
|
|
2659
|
+
<SelectItem value="bimonthly">
|
|
2660
|
+
{t('recurrenceEditor.frequencies.bimonthly')}
|
|
2661
|
+
</SelectItem>
|
|
2662
|
+
<SelectItem value="quarterly">
|
|
2663
|
+
{t('recurrenceEditor.frequencies.quarterly')}
|
|
2664
|
+
</SelectItem>
|
|
2665
|
+
<SelectItem value="semiannual">
|
|
2666
|
+
{t('recurrenceEditor.frequencies.semiannual')}
|
|
2667
|
+
</SelectItem>
|
|
2668
|
+
<SelectItem value="annual">
|
|
2669
|
+
{t('recurrenceEditor.frequencies.annual')}
|
|
2670
|
+
</SelectItem>
|
|
2671
|
+
</SelectContent>
|
|
2672
|
+
</Select>
|
|
2673
|
+
<FormMessage />
|
|
2674
|
+
</FormItem>
|
|
2675
|
+
)}
|
|
2676
|
+
/>
|
|
2677
|
+
|
|
2678
|
+
<FormField
|
|
2679
|
+
control={form.control}
|
|
2680
|
+
name="recorrencia.dataFim"
|
|
2681
|
+
render={({ field }) => (
|
|
2682
|
+
<FormItem>
|
|
2683
|
+
<FormLabel>
|
|
2684
|
+
{t('recurrenceEditor.endDateLabel')}
|
|
2685
|
+
</FormLabel>
|
|
2686
|
+
<FormControl>
|
|
2687
|
+
<Input
|
|
2688
|
+
type="date"
|
|
2689
|
+
{...field}
|
|
2690
|
+
value={field.value || ''}
|
|
2691
|
+
/>
|
|
2692
|
+
</FormControl>
|
|
2693
|
+
<FormMessage />
|
|
2694
|
+
</FormItem>
|
|
2695
|
+
)}
|
|
2696
|
+
/>
|
|
2697
|
+
|
|
2698
|
+
<FormField
|
|
2699
|
+
control={form.control}
|
|
2700
|
+
name="recorrencia.numOcorrencias"
|
|
2701
|
+
render={({ field }) => (
|
|
2702
|
+
<FormItem>
|
|
2703
|
+
<FormLabel>
|
|
2704
|
+
{t('recurrenceEditor.maxOccurrencesLabel')}
|
|
2705
|
+
</FormLabel>
|
|
2706
|
+
<FormControl>
|
|
2707
|
+
<Input
|
|
2708
|
+
type="number"
|
|
2709
|
+
min={1}
|
|
2710
|
+
max={600}
|
|
2711
|
+
{...field}
|
|
2712
|
+
value={field.value ?? ''}
|
|
2713
|
+
onChange={(e) =>
|
|
2714
|
+
field.onChange(
|
|
2715
|
+
e.target.value === ''
|
|
2716
|
+
? undefined
|
|
2717
|
+
: Number(e.target.value)
|
|
2718
|
+
)
|
|
2719
|
+
}
|
|
2720
|
+
/>
|
|
2721
|
+
</FormControl>
|
|
2722
|
+
<FormMessage />
|
|
2723
|
+
</FormItem>
|
|
2724
|
+
)}
|
|
2725
|
+
/>
|
|
2726
|
+
</div>
|
|
2727
|
+
{form.formState.errors.recorrencia?.message && (
|
|
2728
|
+
<p className="text-xs text-destructive">
|
|
2729
|
+
{form.formState.errors.recorrencia.message}
|
|
2730
|
+
</p>
|
|
2731
|
+
)}
|
|
2271
2732
|
</div>
|
|
2272
|
-
|
|
2273
|
-
<p
|
|
2274
|
-
className={`text-xs ${
|
|
2275
|
-
installmentsDiffCents === 0
|
|
2276
|
-
? 'text-muted-foreground'
|
|
2277
|
-
: 'text-destructive'
|
|
2278
|
-
}`}
|
|
2279
|
-
>
|
|
2280
|
-
{t('installmentsEditor.totalPrefix', {
|
|
2281
|
-
total: installmentsTotal.toFixed(2),
|
|
2282
|
-
})}
|
|
2283
|
-
{installmentsDiffCents > 0 &&
|
|
2284
|
-
` ${t('installmentsEditor.adjustmentNeeded')}`}
|
|
2285
|
-
</p>
|
|
2286
|
-
{form.formState.errors.installments?.message && (
|
|
2287
|
-
<p className="text-xs text-destructive">
|
|
2288
|
-
{form.formState.errors.installments.message}
|
|
2289
|
-
</p>
|
|
2290
|
-
)}
|
|
2291
|
-
</div>
|
|
2733
|
+
)}
|
|
2292
2734
|
</FinanceSheetSection>
|
|
2293
2735
|
|
|
2294
2736
|
<FinanceSheetSection
|
|
@@ -2411,7 +2853,7 @@ function EditarTituloSheet({
|
|
|
2411
2853
|
</div>
|
|
2412
2854
|
</form>
|
|
2413
2855
|
</Form>
|
|
2414
|
-
</
|
|
2856
|
+
</ResizableSheetContent>
|
|
2415
2857
|
</Sheet>
|
|
2416
2858
|
);
|
|
2417
2859
|
}
|
|
@@ -2437,6 +2879,7 @@ export default function TitulosReceberPage() {
|
|
|
2437
2879
|
const [fromDate, setFromDate] = useState('');
|
|
2438
2880
|
const [toDate, setToDate] = useState('');
|
|
2439
2881
|
const [page, setPage] = useState(1);
|
|
2882
|
+
const [isCreateSheetOpen, setIsCreateSheetOpen] = useState(false);
|
|
2440
2883
|
const pageSize = 10;
|
|
2441
2884
|
|
|
2442
2885
|
const normalizedStatusFilter =
|
|
@@ -2487,6 +2930,8 @@ export default function TitulosReceberPage() {
|
|
|
2487
2930
|
placeholderData: (old) => old,
|
|
2488
2931
|
});
|
|
2489
2932
|
|
|
2933
|
+
useFinanceRealtimeRefresh({ request, onRefresh: refetchTitles });
|
|
2934
|
+
|
|
2490
2935
|
const titulosReceber = paginatedTitlesResponse?.data || [];
|
|
2491
2936
|
const visibleTitlesTotal = useMemo(
|
|
2492
2937
|
() =>
|
|
@@ -2673,6 +3118,8 @@ export default function TitulosReceberPage() {
|
|
|
2673
3118
|
categorias={categorias}
|
|
2674
3119
|
centrosCusto={centrosCusto}
|
|
2675
3120
|
t={t}
|
|
3121
|
+
open={isCreateSheetOpen}
|
|
3122
|
+
onOpenChange={setIsCreateSheetOpen}
|
|
2676
3123
|
onCreated={async () => {
|
|
2677
3124
|
await Promise.all([refetchTitles(), refetchFinanceData()]);
|
|
2678
3125
|
}}
|
|
@@ -2859,7 +3306,7 @@ export default function TitulosReceberPage() {
|
|
|
2859
3306
|
title={t('empty.title')}
|
|
2860
3307
|
description={t('empty.description')}
|
|
2861
3308
|
actionLabel={t('newTitle.action')}
|
|
2862
|
-
onAction={() =>
|
|
3309
|
+
onAction={() => setIsCreateSheetOpen(true)}
|
|
2863
3310
|
/>
|
|
2864
3311
|
)}
|
|
2865
3312
|
|