@hed-hog/finance 0.0.250 → 0.0.252
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/finance.service.d.ts +1 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +205 -137
- package/dist/finance.service.js.map +1 -1
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +176 -70
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +100 -5
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +19 -20
- package/hedhog/frontend/app/page.tsx.ejs +11 -4
- package/hedhog/frontend/messages/en.json +44 -0
- package/hedhog/frontend/messages/pt.json +44 -0
- package/hedhog/query/constraints.sql +3 -1
- package/package.json +7 -7
- package/src/finance.service.ts +98 -14
|
@@ -21,14 +21,6 @@ import {
|
|
|
21
21
|
CardHeader,
|
|
22
22
|
CardTitle,
|
|
23
23
|
} from '@/components/ui/card';
|
|
24
|
-
import {
|
|
25
|
-
Dialog,
|
|
26
|
-
DialogContent,
|
|
27
|
-
DialogDescription,
|
|
28
|
-
DialogFooter,
|
|
29
|
-
DialogHeader,
|
|
30
|
-
DialogTitle,
|
|
31
|
-
} from '@/components/ui/dialog';
|
|
32
24
|
import {
|
|
33
25
|
DropdownMenu,
|
|
34
26
|
DropdownMenuContent,
|
|
@@ -54,6 +46,13 @@ import {
|
|
|
54
46
|
SelectTrigger,
|
|
55
47
|
SelectValue,
|
|
56
48
|
} from '@/components/ui/select';
|
|
49
|
+
import {
|
|
50
|
+
Sheet,
|
|
51
|
+
SheetContent,
|
|
52
|
+
SheetDescription,
|
|
53
|
+
SheetHeader,
|
|
54
|
+
SheetTitle,
|
|
55
|
+
} from '@/components/ui/sheet';
|
|
57
56
|
import { StatusBadge } from '@/components/ui/status-badge';
|
|
58
57
|
import {
|
|
59
58
|
Table,
|
|
@@ -78,25 +77,33 @@ import {
|
|
|
78
77
|
} from 'lucide-react';
|
|
79
78
|
import { useTranslations } from 'next-intl';
|
|
80
79
|
import Link from 'next/link';
|
|
81
|
-
import { useParams, useRouter } from 'next/navigation';
|
|
82
|
-
import { useEffect, useState } from 'react';
|
|
80
|
+
import { useParams, useRouter, useSearchParams } from 'next/navigation';
|
|
81
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
83
82
|
import { useForm } from 'react-hook-form';
|
|
84
83
|
import { z } from 'zod';
|
|
85
84
|
import { formatarData } from '../../../_lib/formatters';
|
|
86
85
|
import { useFinanceData } from '../../../_lib/use-finance-data';
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
installmentId:
|
|
90
|
-
amount:
|
|
91
|
-
description
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
type SettleFormValues = z.infer<typeof settleSchema>;
|
|
87
|
+
type SettleFormValues = {
|
|
88
|
+
installmentId: string;
|
|
89
|
+
amount: number;
|
|
90
|
+
description?: string;
|
|
91
|
+
};
|
|
95
92
|
|
|
96
93
|
export default function TituloDetalhePage() {
|
|
97
94
|
const t = useTranslations('finance.PayableInstallmentDetailPage');
|
|
95
|
+
const settleSchema = useMemo(
|
|
96
|
+
() =>
|
|
97
|
+
z.object({
|
|
98
|
+
installmentId: z.string().min(1, t('validation.installmentRequired')),
|
|
99
|
+
amount: z.number().min(0.01, t('validation.amountGreaterThanZero')),
|
|
100
|
+
description: z.string().optional(),
|
|
101
|
+
}),
|
|
102
|
+
[t]
|
|
103
|
+
);
|
|
98
104
|
const { request, showToastHandler } = useApp();
|
|
99
105
|
const router = useRouter();
|
|
106
|
+
const searchParams = useSearchParams();
|
|
100
107
|
const params = useParams<{ id: string }>();
|
|
101
108
|
const id = params?.id;
|
|
102
109
|
const { data, refetch } = useFinanceData();
|
|
@@ -111,6 +118,9 @@ export default function TituloDetalhePage() {
|
|
|
111
118
|
} = data;
|
|
112
119
|
|
|
113
120
|
const titulo = titulosPagar.find((t) => t.id === id);
|
|
121
|
+
const canSettle = ['aberto', 'parcial', 'vencido'].includes(
|
|
122
|
+
titulo?.status || ''
|
|
123
|
+
);
|
|
114
124
|
|
|
115
125
|
const settleCandidates = (titulo?.parcelas || []).filter(
|
|
116
126
|
(parcela: any) =>
|
|
@@ -136,6 +146,9 @@ export default function TituloDetalhePage() {
|
|
|
136
146
|
const [isCanceling, setIsCanceling] = useState(false);
|
|
137
147
|
const [isSettleDialogOpen, setIsSettleDialogOpen] = useState(false);
|
|
138
148
|
const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false);
|
|
149
|
+
const [isReverseDialogOpen, setIsReverseDialogOpen] = useState(false);
|
|
150
|
+
const [selectedSettlementIdToReverse, setSelectedSettlementIdToReverse] =
|
|
151
|
+
useState<string | null>(null);
|
|
139
152
|
const [reversingSettlementId, setReversingSettlementId] = useState<
|
|
140
153
|
string | null
|
|
141
154
|
>(null);
|
|
@@ -173,6 +186,18 @@ export default function TituloDetalhePage() {
|
|
|
173
186
|
});
|
|
174
187
|
}, [settleForm, settleCandidates]);
|
|
175
188
|
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const action = searchParams.get('action');
|
|
191
|
+
|
|
192
|
+
if (action !== 'settle') {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (canSettle && settleCandidates.length > 0) {
|
|
197
|
+
setIsSettleDialogOpen(true);
|
|
198
|
+
}
|
|
199
|
+
}, [canSettle, searchParams, settleCandidates.length]);
|
|
200
|
+
|
|
176
201
|
if (!titulo) {
|
|
177
202
|
return (
|
|
178
203
|
<div className="space-y-6">
|
|
@@ -362,8 +387,17 @@ export default function TituloDetalhePage() {
|
|
|
362
387
|
|
|
363
388
|
const canApprove = titulo.status === 'rascunho';
|
|
364
389
|
const canEdit = titulo.status === 'rascunho';
|
|
365
|
-
const canSettle = ['aberto', 'parcial', 'vencido'].includes(titulo.status);
|
|
366
390
|
const canCancel = !['cancelado', 'liquidado'].includes(titulo.status);
|
|
391
|
+
const reversibleSettlements = titulo.parcelas
|
|
392
|
+
.flatMap((parcela: any) => parcela.liquidacoes || [])
|
|
393
|
+
.filter((liquidacao: any) => {
|
|
394
|
+
return (
|
|
395
|
+
!!liquidacao?.settlementId &&
|
|
396
|
+
liquidacao?.status !== 'reversed' &&
|
|
397
|
+
liquidacao?.status !== 'estornado'
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
const canReverse = reversibleSettlements.length > 0;
|
|
367
401
|
|
|
368
402
|
const getErrorMessage = (error: any, fallback: string) => {
|
|
369
403
|
const message = error?.response?.data?.message;
|
|
@@ -392,11 +426,11 @@ export default function TituloDetalhePage() {
|
|
|
392
426
|
});
|
|
393
427
|
|
|
394
428
|
await refetch();
|
|
395
|
-
showToastHandler?.('success', '
|
|
429
|
+
showToastHandler?.('success', t('messages.approveSuccess'));
|
|
396
430
|
} catch (error) {
|
|
397
431
|
showToastHandler?.(
|
|
398
432
|
'error',
|
|
399
|
-
getErrorMessage(error, '
|
|
433
|
+
getErrorMessage(error, t('messages.approveError'))
|
|
400
434
|
);
|
|
401
435
|
} finally {
|
|
402
436
|
setIsApproving(false);
|
|
@@ -422,11 +456,11 @@ export default function TituloDetalhePage() {
|
|
|
422
456
|
|
|
423
457
|
await refetch();
|
|
424
458
|
setIsSettleDialogOpen(false);
|
|
425
|
-
showToastHandler?.('success', '
|
|
459
|
+
showToastHandler?.('success', t('messages.settleSuccess'));
|
|
426
460
|
} catch (error) {
|
|
427
461
|
showToastHandler?.(
|
|
428
462
|
'error',
|
|
429
|
-
getErrorMessage(error, '
|
|
463
|
+
getErrorMessage(error, t('messages.settleError'))
|
|
430
464
|
);
|
|
431
465
|
} finally {
|
|
432
466
|
setIsSettling(false);
|
|
@@ -447,11 +481,11 @@ export default function TituloDetalhePage() {
|
|
|
447
481
|
});
|
|
448
482
|
|
|
449
483
|
await refetch();
|
|
450
|
-
showToastHandler?.('success', '
|
|
484
|
+
showToastHandler?.('success', t('messages.reverseSuccess'));
|
|
451
485
|
} catch (error) {
|
|
452
486
|
showToastHandler?.(
|
|
453
487
|
'error',
|
|
454
|
-
getErrorMessage(error, '
|
|
488
|
+
getErrorMessage(error, t('messages.reverseError'))
|
|
455
489
|
);
|
|
456
490
|
} finally {
|
|
457
491
|
setReversingSettlementId(null);
|
|
@@ -473,11 +507,11 @@ export default function TituloDetalhePage() {
|
|
|
473
507
|
|
|
474
508
|
await refetch();
|
|
475
509
|
setIsCancelDialogOpen(false);
|
|
476
|
-
showToastHandler?.('success', '
|
|
510
|
+
showToastHandler?.('success', t('messages.cancelSuccess'));
|
|
477
511
|
} catch (error) {
|
|
478
512
|
showToastHandler?.(
|
|
479
513
|
'error',
|
|
480
|
-
getErrorMessage(error, '
|
|
514
|
+
getErrorMessage(error, t('messages.cancelError'))
|
|
481
515
|
);
|
|
482
516
|
} finally {
|
|
483
517
|
setIsCanceling(false);
|
|
@@ -533,7 +567,20 @@ export default function TituloDetalhePage() {
|
|
|
533
567
|
<Download className="mr-2 h-4 w-4" />
|
|
534
568
|
{t('actions.settle')}
|
|
535
569
|
</DropdownMenuItem>
|
|
536
|
-
<DropdownMenuItem
|
|
570
|
+
<DropdownMenuItem
|
|
571
|
+
disabled={!canReverse || !!reversingSettlementId}
|
|
572
|
+
onClick={() => {
|
|
573
|
+
const latestSettlement = reversibleSettlements.at(-1);
|
|
574
|
+
if (!latestSettlement?.settlementId) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
setSelectedSettlementIdToReverse(
|
|
579
|
+
String(latestSettlement.settlementId)
|
|
580
|
+
);
|
|
581
|
+
setIsReverseDialogOpen(true);
|
|
582
|
+
}}
|
|
583
|
+
>
|
|
537
584
|
<Undo className="mr-2 h-4 w-4" />
|
|
538
585
|
{t('actions.reverse')}
|
|
539
586
|
</DropdownMenuItem>
|
|
@@ -555,42 +602,88 @@ export default function TituloDetalhePage() {
|
|
|
555
602
|
>
|
|
556
603
|
<AlertDialogContent>
|
|
557
604
|
<AlertDialogHeader>
|
|
558
|
-
<AlertDialogTitle>
|
|
605
|
+
<AlertDialogTitle>
|
|
606
|
+
{t('dialogs.cancel.title')}
|
|
607
|
+
</AlertDialogTitle>
|
|
559
608
|
<AlertDialogDescription>
|
|
560
|
-
|
|
561
|
-
registros de auditoria.
|
|
609
|
+
{t('dialogs.cancel.description')}
|
|
562
610
|
</AlertDialogDescription>
|
|
563
611
|
</AlertDialogHeader>
|
|
564
612
|
<AlertDialogFooter>
|
|
565
613
|
<AlertDialogCancel disabled={isCanceling}>
|
|
566
|
-
|
|
614
|
+
{t('dialogs.cancel.cancel')}
|
|
567
615
|
</AlertDialogCancel>
|
|
568
616
|
<AlertDialogAction
|
|
569
617
|
disabled={isCanceling}
|
|
570
618
|
onClick={() => void handleCancel()}
|
|
571
619
|
>
|
|
572
|
-
|
|
620
|
+
{t('dialogs.cancel.confirm')}
|
|
621
|
+
</AlertDialogAction>
|
|
622
|
+
</AlertDialogFooter>
|
|
623
|
+
</AlertDialogContent>
|
|
624
|
+
</AlertDialog>
|
|
625
|
+
|
|
626
|
+
<AlertDialog
|
|
627
|
+
open={isReverseDialogOpen}
|
|
628
|
+
onOpenChange={(open) => {
|
|
629
|
+
setIsReverseDialogOpen(open);
|
|
630
|
+
|
|
631
|
+
if (!open) {
|
|
632
|
+
setSelectedSettlementIdToReverse(null);
|
|
633
|
+
}
|
|
634
|
+
}}
|
|
635
|
+
>
|
|
636
|
+
<AlertDialogContent>
|
|
637
|
+
<AlertDialogHeader>
|
|
638
|
+
<AlertDialogTitle>
|
|
639
|
+
{t('dialogs.reverse.title')}
|
|
640
|
+
</AlertDialogTitle>
|
|
641
|
+
<AlertDialogDescription>
|
|
642
|
+
{t('dialogs.reverse.description')}
|
|
643
|
+
</AlertDialogDescription>
|
|
644
|
+
</AlertDialogHeader>
|
|
645
|
+
<AlertDialogFooter>
|
|
646
|
+
<AlertDialogCancel disabled={!!reversingSettlementId}>
|
|
647
|
+
{t('dialogs.reverse.cancel')}
|
|
648
|
+
</AlertDialogCancel>
|
|
649
|
+
<AlertDialogAction
|
|
650
|
+
disabled={
|
|
651
|
+
!!reversingSettlementId || !selectedSettlementIdToReverse
|
|
652
|
+
}
|
|
653
|
+
onClick={() => {
|
|
654
|
+
if (!selectedSettlementIdToReverse) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
void handleReverseSettlement(
|
|
659
|
+
selectedSettlementIdToReverse
|
|
660
|
+
).finally(() => {
|
|
661
|
+
setIsReverseDialogOpen(false);
|
|
662
|
+
setSelectedSettlementIdToReverse(null);
|
|
663
|
+
});
|
|
664
|
+
}}
|
|
665
|
+
>
|
|
666
|
+
{t('dialogs.reverse.confirm')}
|
|
573
667
|
</AlertDialogAction>
|
|
574
668
|
</AlertDialogFooter>
|
|
575
669
|
</AlertDialogContent>
|
|
576
670
|
</AlertDialog>
|
|
577
671
|
|
|
578
|
-
<
|
|
672
|
+
<Sheet
|
|
579
673
|
open={isSettleDialogOpen}
|
|
580
674
|
onOpenChange={setIsSettleDialogOpen}
|
|
581
675
|
>
|
|
582
|
-
<
|
|
583
|
-
<
|
|
584
|
-
<
|
|
585
|
-
<
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
</DialogHeader>
|
|
676
|
+
<SheetContent className="w-full sm:max-w-lg overflow-y-auto">
|
|
677
|
+
<SheetHeader>
|
|
678
|
+
<SheetTitle>{t('settleSheet.title')}</SheetTitle>
|
|
679
|
+
<SheetDescription>
|
|
680
|
+
{t('settleSheet.description')}
|
|
681
|
+
</SheetDescription>
|
|
682
|
+
</SheetHeader>
|
|
590
683
|
|
|
591
684
|
<Form {...settleForm}>
|
|
592
685
|
<form
|
|
593
|
-
className="space-y-4"
|
|
686
|
+
className="space-y-4 px-4"
|
|
594
687
|
onSubmit={settleForm.handleSubmit(handleSettle)}
|
|
595
688
|
>
|
|
596
689
|
<FormField
|
|
@@ -598,7 +691,9 @@ export default function TituloDetalhePage() {
|
|
|
598
691
|
name="installmentId"
|
|
599
692
|
render={({ field }) => (
|
|
600
693
|
<FormItem>
|
|
601
|
-
<FormLabel>
|
|
694
|
+
<FormLabel>
|
|
695
|
+
{t('settleSheet.installmentLabel')}
|
|
696
|
+
</FormLabel>
|
|
602
697
|
<Select
|
|
603
698
|
value={field.value}
|
|
604
699
|
onValueChange={(value) => {
|
|
@@ -618,18 +713,24 @@ export default function TituloDetalhePage() {
|
|
|
618
713
|
}}
|
|
619
714
|
>
|
|
620
715
|
<FormControl>
|
|
621
|
-
<SelectTrigger>
|
|
622
|
-
<SelectValue
|
|
716
|
+
<SelectTrigger className="w-full">
|
|
717
|
+
<SelectValue
|
|
718
|
+
placeholder={t(
|
|
719
|
+
'settleSheet.installmentPlaceholder'
|
|
720
|
+
)}
|
|
721
|
+
/>
|
|
623
722
|
</SelectTrigger>
|
|
624
723
|
</FormControl>
|
|
625
724
|
<SelectContent>
|
|
626
725
|
{settleCandidates.map((parcela: any) => (
|
|
627
726
|
<SelectItem key={parcela.id} value={parcela.id}>
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
727
|
+
{t('settleSheet.installmentOption', {
|
|
728
|
+
number: parcela.numero,
|
|
729
|
+
amount: new Intl.NumberFormat('pt-BR', {
|
|
730
|
+
style: 'currency',
|
|
731
|
+
currency: 'BRL',
|
|
732
|
+
}).format(Number(parcela.valorAberto || 0)),
|
|
733
|
+
})}
|
|
633
734
|
</SelectItem>
|
|
634
735
|
))}
|
|
635
736
|
</SelectContent>
|
|
@@ -644,7 +745,7 @@ export default function TituloDetalhePage() {
|
|
|
644
745
|
name="amount"
|
|
645
746
|
render={({ field }) => (
|
|
646
747
|
<FormItem>
|
|
647
|
-
<FormLabel>
|
|
748
|
+
<FormLabel>{t('settleSheet.amountLabel')}</FormLabel>
|
|
648
749
|
<FormControl>
|
|
649
750
|
<InputMoney
|
|
650
751
|
value={Number(field.value || 0)}
|
|
@@ -663,7 +764,9 @@ export default function TituloDetalhePage() {
|
|
|
663
764
|
name="description"
|
|
664
765
|
render={({ field }) => (
|
|
665
766
|
<FormItem>
|
|
666
|
-
<FormLabel>
|
|
767
|
+
<FormLabel>
|
|
768
|
+
{t('settleSheet.descriptionLabel')}
|
|
769
|
+
</FormLabel>
|
|
667
770
|
<FormControl>
|
|
668
771
|
<Input {...field} value={field.value || ''} />
|
|
669
772
|
</FormControl>
|
|
@@ -672,23 +775,19 @@ export default function TituloDetalhePage() {
|
|
|
672
775
|
)}
|
|
673
776
|
/>
|
|
674
777
|
|
|
675
|
-
<
|
|
778
|
+
<div className="flex flex-col gap-4">
|
|
676
779
|
<Button
|
|
677
|
-
|
|
678
|
-
|
|
780
|
+
className="w-full"
|
|
781
|
+
type="submit"
|
|
679
782
|
disabled={isSettling}
|
|
680
|
-
onClick={() => setIsSettleDialogOpen(false)}
|
|
681
783
|
>
|
|
682
|
-
|
|
683
|
-
</Button>
|
|
684
|
-
<Button type="submit" disabled={isSettling}>
|
|
685
|
-
Confirmar baixa
|
|
784
|
+
{t('settleSheet.confirm')}
|
|
686
785
|
</Button>
|
|
687
|
-
</
|
|
786
|
+
</div>
|
|
688
787
|
</form>
|
|
689
788
|
</Form>
|
|
690
|
-
</
|
|
691
|
-
</
|
|
789
|
+
</SheetContent>
|
|
790
|
+
</Sheet>
|
|
692
791
|
</div>
|
|
693
792
|
}
|
|
694
793
|
/>
|
|
@@ -905,7 +1004,9 @@ export default function TituloDetalhePage() {
|
|
|
905
1004
|
</TableHead>
|
|
906
1005
|
<TableHead>{t('settlementsTable.account')}</TableHead>
|
|
907
1006
|
<TableHead>{t('settlementsTable.method')}</TableHead>
|
|
908
|
-
<TableHead className="text-right">
|
|
1007
|
+
<TableHead className="text-right">
|
|
1008
|
+
{t('settlementsTable.actions')}
|
|
1009
|
+
</TableHead>
|
|
909
1010
|
</TableRow>
|
|
910
1011
|
</TableHeader>
|
|
911
1012
|
<TableBody>
|
|
@@ -944,22 +1045,25 @@ export default function TituloDetalhePage() {
|
|
|
944
1045
|
}
|
|
945
1046
|
>
|
|
946
1047
|
<Undo className="mr-2 h-4 w-4" />
|
|
947
|
-
|
|
1048
|
+
{t('settlementsTable.reverseButton')}
|
|
948
1049
|
</Button>
|
|
949
1050
|
</AlertDialogTrigger>
|
|
950
1051
|
<AlertDialogContent>
|
|
951
1052
|
<AlertDialogHeader>
|
|
952
1053
|
<AlertDialogTitle>
|
|
953
|
-
|
|
1054
|
+
{t('settlementsTable.reverseDialogTitle')}
|
|
954
1055
|
</AlertDialogTitle>
|
|
955
1056
|
<AlertDialogDescription>
|
|
956
|
-
|
|
957
|
-
|
|
1057
|
+
{t(
|
|
1058
|
+
'settlementsTable.reverseDialogDescription'
|
|
1059
|
+
)}
|
|
958
1060
|
</AlertDialogDescription>
|
|
959
1061
|
</AlertDialogHeader>
|
|
960
1062
|
<AlertDialogFooter>
|
|
961
1063
|
<AlertDialogCancel>
|
|
962
|
-
|
|
1064
|
+
{t(
|
|
1065
|
+
'settlementsTable.reverseDialogCancel'
|
|
1066
|
+
)}
|
|
963
1067
|
</AlertDialogCancel>
|
|
964
1068
|
<AlertDialogAction
|
|
965
1069
|
onClick={() =>
|
|
@@ -968,7 +1072,9 @@ export default function TituloDetalhePage() {
|
|
|
968
1072
|
)
|
|
969
1073
|
}
|
|
970
1074
|
>
|
|
971
|
-
|
|
1075
|
+
{t(
|
|
1076
|
+
'settlementsTable.reverseDialogConfirm'
|
|
1077
|
+
)}
|
|
972
1078
|
</AlertDialogAction>
|
|
973
1079
|
</AlertDialogFooter>
|
|
974
1080
|
</AlertDialogContent>
|
|
@@ -1729,6 +1729,8 @@ export default function TitulosPagarPage() {
|
|
|
1729
1729
|
const [search, setSearch] = useState('');
|
|
1730
1730
|
const [statusFilter, setStatusFilter] = useState<string>('');
|
|
1731
1731
|
const [editingTitleId, setEditingTitleId] = useState<string | null>(null);
|
|
1732
|
+
const [approvingTitleId, setApprovingTitleId] = useState<string | null>(null);
|
|
1733
|
+
const [reversingTitleId, setReversingTitleId] = useState<string | null>(null);
|
|
1732
1734
|
const [cancelingTitleId, setCancelingTitleId] = useState<string | null>(null);
|
|
1733
1735
|
|
|
1734
1736
|
const editingTitle = useMemo(
|
|
@@ -1792,13 +1794,97 @@ export default function TitulosPagarPage() {
|
|
|
1792
1794
|
|
|
1793
1795
|
await refetch();
|
|
1794
1796
|
showToastHandler?.('success', 'Título cancelado com sucesso');
|
|
1795
|
-
} catch {
|
|
1796
|
-
|
|
1797
|
+
} catch (error: any) {
|
|
1798
|
+
const message = error?.response?.data?.message;
|
|
1799
|
+
showToastHandler?.(
|
|
1800
|
+
'error',
|
|
1801
|
+
typeof message === 'string' && message.trim()
|
|
1802
|
+
? message
|
|
1803
|
+
: 'Não foi possível cancelar o título'
|
|
1804
|
+
);
|
|
1797
1805
|
} finally {
|
|
1798
1806
|
setCancelingTitleId(null);
|
|
1799
1807
|
}
|
|
1800
1808
|
};
|
|
1801
1809
|
|
|
1810
|
+
const handleApproveTitle = async (titleId: string) => {
|
|
1811
|
+
if (!titleId || approvingTitleId) {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
setApprovingTitleId(titleId);
|
|
1816
|
+
try {
|
|
1817
|
+
await request({
|
|
1818
|
+
url: `/finance/accounts-payable/installments/${titleId}/approve`,
|
|
1819
|
+
method: 'PATCH',
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
await refetch();
|
|
1823
|
+
showToastHandler?.('success', 'Título aprovado com sucesso');
|
|
1824
|
+
} catch (error: any) {
|
|
1825
|
+
const message = error?.response?.data?.message;
|
|
1826
|
+
showToastHandler?.(
|
|
1827
|
+
'error',
|
|
1828
|
+
typeof message === 'string' && message.trim()
|
|
1829
|
+
? message
|
|
1830
|
+
: 'Não foi possível aprovar o título'
|
|
1831
|
+
);
|
|
1832
|
+
} finally {
|
|
1833
|
+
setApprovingTitleId(null);
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
|
|
1837
|
+
const handleSettleTitle = (titleId: string) => {
|
|
1838
|
+
router.push(
|
|
1839
|
+
`/finance/accounts-payable/installments/${titleId}?action=settle`
|
|
1840
|
+
);
|
|
1841
|
+
};
|
|
1842
|
+
|
|
1843
|
+
const handleReverseTitle = async (title: any) => {
|
|
1844
|
+
const titleId = title?.id;
|
|
1845
|
+
|
|
1846
|
+
if (!titleId || reversingTitleId) {
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
const settlementToReverse = (title?.parcelas || [])
|
|
1851
|
+
.flatMap((parcela: any) => parcela?.liquidacoes || [])
|
|
1852
|
+
.find((liquidacao: any) => {
|
|
1853
|
+
return (
|
|
1854
|
+
!!liquidacao?.settlementId &&
|
|
1855
|
+
liquidacao?.status !== 'reversed' &&
|
|
1856
|
+
liquidacao?.status !== 'estornado'
|
|
1857
|
+
);
|
|
1858
|
+
});
|
|
1859
|
+
|
|
1860
|
+
if (!settlementToReverse?.settlementId) {
|
|
1861
|
+
showToastHandler?.('error', 'Nenhuma liquidação ativa para estornar');
|
|
1862
|
+
return;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
setReversingTitleId(titleId);
|
|
1866
|
+
try {
|
|
1867
|
+
await request({
|
|
1868
|
+
url: `/finance/accounts-payable/installments/${titleId}/settlements/${settlementToReverse.settlementId}/reverse`,
|
|
1869
|
+
method: 'PATCH',
|
|
1870
|
+
data: {},
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
await refetch();
|
|
1874
|
+
showToastHandler?.('success', 'Estorno realizado com sucesso');
|
|
1875
|
+
} catch (error: any) {
|
|
1876
|
+
const message = error?.response?.data?.message;
|
|
1877
|
+
showToastHandler?.(
|
|
1878
|
+
'error',
|
|
1879
|
+
typeof message === 'string' && message.trim()
|
|
1880
|
+
? message
|
|
1881
|
+
: 'Não foi possível estornar a liquidação'
|
|
1882
|
+
);
|
|
1883
|
+
} finally {
|
|
1884
|
+
setReversingTitleId(null);
|
|
1885
|
+
}
|
|
1886
|
+
};
|
|
1887
|
+
|
|
1802
1888
|
const filteredTitulos = titulosPagar.filter((titulo) => {
|
|
1803
1889
|
const matchesSearch =
|
|
1804
1890
|
titulo.documento.toLowerCase().includes(search.toLowerCase()) ||
|
|
@@ -1985,23 +2071,32 @@ export default function TitulosPagarPage() {
|
|
|
1985
2071
|
</DropdownMenuItem>
|
|
1986
2072
|
<DropdownMenuSeparator />
|
|
1987
2073
|
<DropdownMenuItem
|
|
1988
|
-
disabled={
|
|
2074
|
+
disabled={
|
|
2075
|
+
titulo.status !== 'rascunho' ||
|
|
2076
|
+
approvingTitleId === titulo.id
|
|
2077
|
+
}
|
|
2078
|
+
onClick={() => void handleApproveTitle(titulo.id)}
|
|
1989
2079
|
>
|
|
1990
2080
|
<CheckCircle className="mr-2 h-4 w-4" />
|
|
1991
2081
|
{t('table.actions.approve')}
|
|
1992
2082
|
</DropdownMenuItem>
|
|
1993
2083
|
<DropdownMenuItem
|
|
1994
2084
|
disabled={
|
|
1995
|
-
!['aberto', 'parcial'].includes(
|
|
2085
|
+
!['aberto', 'parcial', 'vencido'].includes(
|
|
2086
|
+
titulo.status
|
|
2087
|
+
)
|
|
1996
2088
|
}
|
|
2089
|
+
onClick={() => handleSettleTitle(titulo.id)}
|
|
1997
2090
|
>
|
|
1998
2091
|
<Download className="mr-2 h-4 w-4" />
|
|
1999
2092
|
{t('table.actions.settle')}
|
|
2000
2093
|
</DropdownMenuItem>
|
|
2001
2094
|
<DropdownMenuItem
|
|
2002
2095
|
disabled={
|
|
2003
|
-
!['parcial', 'liquidado'].includes(titulo.status)
|
|
2096
|
+
!['parcial', 'liquidado'].includes(titulo.status) ||
|
|
2097
|
+
reversingTitleId === titulo.id
|
|
2004
2098
|
}
|
|
2099
|
+
onClick={() => void handleReverseTitle(titulo)}
|
|
2005
2100
|
>
|
|
2006
2101
|
<Undo className="mr-2 h-4 w-4" />
|
|
2007
2102
|
{t('table.actions.reverse')}
|
|
@@ -22,14 +22,6 @@ import {
|
|
|
22
22
|
CardHeader,
|
|
23
23
|
CardTitle,
|
|
24
24
|
} from '@/components/ui/card';
|
|
25
|
-
import {
|
|
26
|
-
Dialog,
|
|
27
|
-
DialogContent,
|
|
28
|
-
DialogDescription,
|
|
29
|
-
DialogFooter,
|
|
30
|
-
DialogHeader,
|
|
31
|
-
DialogTitle,
|
|
32
|
-
} from '@/components/ui/dialog';
|
|
33
25
|
import {
|
|
34
26
|
DropdownMenu,
|
|
35
27
|
DropdownMenuContent,
|
|
@@ -54,6 +46,13 @@ import {
|
|
|
54
46
|
SelectTrigger,
|
|
55
47
|
SelectValue,
|
|
56
48
|
} from '@/components/ui/select';
|
|
49
|
+
import {
|
|
50
|
+
Sheet,
|
|
51
|
+
SheetContent,
|
|
52
|
+
SheetDescription,
|
|
53
|
+
SheetHeader,
|
|
54
|
+
SheetTitle,
|
|
55
|
+
} from '@/components/ui/sheet';
|
|
57
56
|
import { StatusBadge } from '@/components/ui/status-badge';
|
|
58
57
|
import {
|
|
59
58
|
Table,
|
|
@@ -522,21 +521,21 @@ export default function TituloReceberDetalhePage() {
|
|
|
522
521
|
</DropdownMenuContent>
|
|
523
522
|
</DropdownMenu>
|
|
524
523
|
|
|
525
|
-
<
|
|
524
|
+
<Sheet
|
|
526
525
|
open={isSettleDialogOpen}
|
|
527
526
|
onOpenChange={setIsSettleDialogOpen}
|
|
528
527
|
>
|
|
529
|
-
<
|
|
530
|
-
<
|
|
531
|
-
<
|
|
532
|
-
<
|
|
528
|
+
<SheetContent className="w-full sm:max-w-lg overflow-y-auto">
|
|
529
|
+
<SheetHeader>
|
|
530
|
+
<SheetTitle>Registrar recebimento</SheetTitle>
|
|
531
|
+
<SheetDescription>
|
|
533
532
|
Informe a parcela e o valor para baixa parcial ou total.
|
|
534
|
-
</
|
|
535
|
-
</
|
|
533
|
+
</SheetDescription>
|
|
534
|
+
</SheetHeader>
|
|
536
535
|
|
|
537
536
|
<Form {...settleForm}>
|
|
538
537
|
<form
|
|
539
|
-
className="space-y-4"
|
|
538
|
+
className="space-y-4 px-1"
|
|
540
539
|
onSubmit={settleForm.handleSubmit(handleSettle)}
|
|
541
540
|
>
|
|
542
541
|
<FormField
|
|
@@ -618,7 +617,7 @@ export default function TituloReceberDetalhePage() {
|
|
|
618
617
|
)}
|
|
619
618
|
/>
|
|
620
619
|
|
|
621
|
-
<
|
|
620
|
+
<div className="flex justify-end gap-2 pt-2">
|
|
622
621
|
<Button
|
|
623
622
|
type="button"
|
|
624
623
|
variant="outline"
|
|
@@ -630,11 +629,11 @@ export default function TituloReceberDetalhePage() {
|
|
|
630
629
|
<Button type="submit" disabled={isSettling}>
|
|
631
630
|
Confirmar recebimento
|
|
632
631
|
</Button>
|
|
633
|
-
</
|
|
632
|
+
</div>
|
|
634
633
|
</form>
|
|
635
634
|
</Form>
|
|
636
|
-
</
|
|
637
|
-
</
|
|
635
|
+
</SheetContent>
|
|
636
|
+
</Sheet>
|
|
638
637
|
</div>
|
|
639
638
|
}
|
|
640
639
|
/>
|