@hed-hog/finance 0.0.238 → 0.0.240
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/README.md +1 -22
- package/dist/dto/reject-title.dto.d.ts +4 -0
- package/dist/dto/reject-title.dto.d.ts.map +1 -0
- package/dist/dto/reject-title.dto.js +22 -0
- package/dist/dto/reject-title.dto.js.map +1 -0
- package/dist/dto/reverse-settlement.dto.d.ts +4 -0
- package/dist/dto/reverse-settlement.dto.d.ts.map +1 -0
- package/dist/dto/reverse-settlement.dto.js +22 -0
- package/dist/dto/reverse-settlement.dto.js.map +1 -0
- package/dist/dto/settle-installment.dto.d.ts +12 -0
- package/dist/dto/settle-installment.dto.d.ts.map +1 -0
- package/dist/dto/settle-installment.dto.js +71 -0
- package/dist/dto/settle-installment.dto.js.map +1 -0
- package/dist/finance-data.controller.d.ts +13 -5
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.d.ts +380 -12
- package/dist/finance-installments.controller.d.ts.map +1 -1
- package/dist/finance-installments.controller.js +144 -0
- package/dist/finance-installments.controller.js.map +1 -1
- package/dist/finance-statements.controller.d.ts +8 -0
- package/dist/finance-statements.controller.d.ts.map +1 -1
- package/dist/finance-statements.controller.js +40 -0
- package/dist/finance-statements.controller.js.map +1 -1
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +1 -0
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +435 -19
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +1286 -80
- package/dist/finance.service.js.map +1 -1
- package/hedhog/data/route.yaml +117 -0
- package/hedhog/frontend/app/accounts-payable/approvals/page.tsx.ejs +92 -12
- package/hedhog/frontend/app/accounts-payable/installments/[id]/page.tsx.ejs +434 -7
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +1172 -25
- package/hedhog/frontend/app/accounts-receivable/installments/[id]/page.tsx.ejs +396 -49
- package/hedhog/frontend/app/accounts-receivable/installments/page.tsx.ejs +430 -14
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +212 -60
- package/hedhog/frontend/messages/en.json +1 -0
- package/hedhog/frontend/messages/pt.json +1 -0
- package/hedhog/query/0_constraints.sql +2 -0
- package/hedhog/query/constraints.sql +86 -0
- package/hedhog/table/bank_account.yaml +0 -8
- package/hedhog/table/financial_title.yaml +1 -9
- package/hedhog/table/settlement.yaml +0 -8
- package/package.json +6 -6
- package/src/dto/reject-title.dto.ts +7 -0
- package/src/dto/reverse-settlement.dto.ts +7 -0
- package/src/dto/settle-installment.dto.ts +55 -0
- package/src/finance-installments.controller.ts +172 -10
- package/src/finance-statements.controller.ts +61 -2
- package/src/finance.module.ts +2 -1
- package/src/finance.service.ts +1887 -106
- package/hedhog/table/branch.yaml +0 -18
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Page, PageHeader } from '@/components/entity-list';
|
|
4
|
+
import {
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogAction,
|
|
7
|
+
AlertDialogCancel,
|
|
8
|
+
AlertDialogContent,
|
|
9
|
+
AlertDialogDescription,
|
|
10
|
+
AlertDialogFooter,
|
|
11
|
+
AlertDialogHeader,
|
|
12
|
+
AlertDialogTitle,
|
|
13
|
+
AlertDialogTrigger,
|
|
14
|
+
} from '@/components/ui/alert-dialog';
|
|
4
15
|
import { AuditTimeline } from '@/components/ui/audit-timeline';
|
|
5
16
|
import { Button } from '@/components/ui/button';
|
|
6
17
|
import {
|
|
@@ -10,6 +21,14 @@ import {
|
|
|
10
21
|
CardHeader,
|
|
11
22
|
CardTitle,
|
|
12
23
|
} from '@/components/ui/card';
|
|
24
|
+
import {
|
|
25
|
+
Dialog,
|
|
26
|
+
DialogContent,
|
|
27
|
+
DialogDescription,
|
|
28
|
+
DialogFooter,
|
|
29
|
+
DialogHeader,
|
|
30
|
+
DialogTitle,
|
|
31
|
+
} from '@/components/ui/dialog';
|
|
13
32
|
import {
|
|
14
33
|
DropdownMenu,
|
|
15
34
|
DropdownMenuContent,
|
|
@@ -17,7 +36,24 @@ import {
|
|
|
17
36
|
DropdownMenuSeparator,
|
|
18
37
|
DropdownMenuTrigger,
|
|
19
38
|
} from '@/components/ui/dropdown-menu';
|
|
39
|
+
import {
|
|
40
|
+
Form,
|
|
41
|
+
FormControl,
|
|
42
|
+
FormField,
|
|
43
|
+
FormItem,
|
|
44
|
+
FormLabel,
|
|
45
|
+
FormMessage,
|
|
46
|
+
} from '@/components/ui/form';
|
|
47
|
+
import { Input } from '@/components/ui/input';
|
|
48
|
+
import { InputMoney } from '@/components/ui/input-money';
|
|
20
49
|
import { Money } from '@/components/ui/money';
|
|
50
|
+
import {
|
|
51
|
+
Select,
|
|
52
|
+
SelectContent,
|
|
53
|
+
SelectItem,
|
|
54
|
+
SelectTrigger,
|
|
55
|
+
SelectValue,
|
|
56
|
+
} from '@/components/ui/select';
|
|
21
57
|
import { StatusBadge } from '@/components/ui/status-badge';
|
|
22
58
|
import {
|
|
23
59
|
Table,
|
|
@@ -30,6 +66,7 @@ import {
|
|
|
30
66
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
31
67
|
import { TagSelectorSheet } from '@/components/ui/tag-selector-sheet';
|
|
32
68
|
import { useApp } from '@hed-hog/next-app-provider';
|
|
69
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
33
70
|
import {
|
|
34
71
|
CheckCircle,
|
|
35
72
|
Download,
|
|
@@ -41,17 +78,28 @@ import {
|
|
|
41
78
|
} from 'lucide-react';
|
|
42
79
|
import { useTranslations } from 'next-intl';
|
|
43
80
|
import Link from 'next/link';
|
|
44
|
-
import { useParams } from 'next/navigation';
|
|
81
|
+
import { useParams, useRouter } from 'next/navigation';
|
|
45
82
|
import { useEffect, useState } from 'react';
|
|
83
|
+
import { useForm } from 'react-hook-form';
|
|
84
|
+
import { z } from 'zod';
|
|
46
85
|
import { formatarData } from '../../../_lib/formatters';
|
|
47
86
|
import { useFinanceData } from '../../../_lib/use-finance-data';
|
|
48
87
|
|
|
88
|
+
const settleSchema = z.object({
|
|
89
|
+
installmentId: z.string().min(1, 'Parcela obrigatória'),
|
|
90
|
+
amount: z.number().min(0.01, 'Valor deve ser maior que zero'),
|
|
91
|
+
description: z.string().optional(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
type SettleFormValues = z.infer<typeof settleSchema>;
|
|
95
|
+
|
|
49
96
|
export default function TituloDetalhePage() {
|
|
50
97
|
const t = useTranslations('finance.PayableInstallmentDetailPage');
|
|
51
98
|
const { request, showToastHandler } = useApp();
|
|
99
|
+
const router = useRouter();
|
|
52
100
|
const params = useParams<{ id: string }>();
|
|
53
101
|
const id = params?.id;
|
|
54
|
-
const { data } = useFinanceData();
|
|
102
|
+
const { data, refetch } = useFinanceData();
|
|
55
103
|
const {
|
|
56
104
|
titulosPagar,
|
|
57
105
|
pessoas,
|
|
@@ -64,9 +112,33 @@ export default function TituloDetalhePage() {
|
|
|
64
112
|
|
|
65
113
|
const titulo = titulosPagar.find((t) => t.id === id);
|
|
66
114
|
|
|
115
|
+
const settleCandidates = (titulo?.parcelas || []).filter(
|
|
116
|
+
(parcela: any) =>
|
|
117
|
+
parcela.status === 'aberto' ||
|
|
118
|
+
parcela.status === 'parcial' ||
|
|
119
|
+
parcela.status === 'vencido'
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const settleForm = useForm<SettleFormValues>({
|
|
123
|
+
resolver: zodResolver(settleSchema),
|
|
124
|
+
defaultValues: {
|
|
125
|
+
installmentId: settleCandidates[0]?.id || '',
|
|
126
|
+
amount: Number(settleCandidates[0]?.valorAberto || 0),
|
|
127
|
+
description: '',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
67
131
|
const [availableTags, setAvailableTags] = useState<any[]>([]);
|
|
68
132
|
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([]);
|
|
69
133
|
const [isCreatingTag, setIsCreatingTag] = useState(false);
|
|
134
|
+
const [isApproving, setIsApproving] = useState(false);
|
|
135
|
+
const [isSettling, setIsSettling] = useState(false);
|
|
136
|
+
const [isCanceling, setIsCanceling] = useState(false);
|
|
137
|
+
const [isSettleDialogOpen, setIsSettleDialogOpen] = useState(false);
|
|
138
|
+
const [isCancelDialogOpen, setIsCancelDialogOpen] = useState(false);
|
|
139
|
+
const [reversingSettlementId, setReversingSettlementId] = useState<
|
|
140
|
+
string | null
|
|
141
|
+
>(null);
|
|
70
142
|
|
|
71
143
|
useEffect(() => {
|
|
72
144
|
setAvailableTags(tags || []);
|
|
@@ -80,6 +152,27 @@ export default function TituloDetalhePage() {
|
|
|
80
152
|
setSelectedTagIds(Array.isArray(titulo.tags) ? titulo.tags : []);
|
|
81
153
|
}, [titulo]);
|
|
82
154
|
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const firstCandidate = settleCandidates[0];
|
|
157
|
+
const nextInstallmentId = firstCandidate?.id || '';
|
|
158
|
+
const nextAmount = Number(firstCandidate?.valorAberto || 0);
|
|
159
|
+
const currentInstallmentId = settleForm.getValues('installmentId') || '';
|
|
160
|
+
const currentAmount = Number(settleForm.getValues('amount') || 0);
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
currentInstallmentId === nextInstallmentId &&
|
|
164
|
+
currentAmount === nextAmount
|
|
165
|
+
) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
settleForm.reset({
|
|
170
|
+
installmentId: nextInstallmentId,
|
|
171
|
+
amount: nextAmount,
|
|
172
|
+
description: '',
|
|
173
|
+
});
|
|
174
|
+
}, [settleForm, settleCandidates]);
|
|
175
|
+
|
|
83
176
|
if (!titulo) {
|
|
84
177
|
return (
|
|
85
178
|
<div className="space-y-6">
|
|
@@ -267,6 +360,130 @@ export default function TituloDetalhePage() {
|
|
|
267
360
|
}
|
|
268
361
|
};
|
|
269
362
|
|
|
363
|
+
const canApprove = titulo.status === 'rascunho';
|
|
364
|
+
const canEdit = titulo.status === 'rascunho';
|
|
365
|
+
const canSettle = ['aberto', 'parcial', 'vencido'].includes(titulo.status);
|
|
366
|
+
const canCancel = !['cancelado', 'liquidado'].includes(titulo.status);
|
|
367
|
+
|
|
368
|
+
const getErrorMessage = (error: any, fallback: string) => {
|
|
369
|
+
const message = error?.response?.data?.message;
|
|
370
|
+
|
|
371
|
+
if (Array.isArray(message)) {
|
|
372
|
+
return message.join(', ');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (typeof message === 'string' && message.trim()) {
|
|
376
|
+
return message;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return fallback;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const handleApprove = async () => {
|
|
383
|
+
if (!canApprove || isApproving) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
setIsApproving(true);
|
|
388
|
+
try {
|
|
389
|
+
await request({
|
|
390
|
+
url: `/finance/accounts-payable/installments/${titulo.id}/approve`,
|
|
391
|
+
method: 'PATCH',
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
await refetch();
|
|
395
|
+
showToastHandler?.('success', 'Título aprovado com sucesso');
|
|
396
|
+
} catch (error) {
|
|
397
|
+
showToastHandler?.(
|
|
398
|
+
'error',
|
|
399
|
+
getErrorMessage(error, 'Não foi possível aprovar o título')
|
|
400
|
+
);
|
|
401
|
+
} finally {
|
|
402
|
+
setIsApproving(false);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const handleSettle = async (values: SettleFormValues) => {
|
|
407
|
+
if (!canSettle || isSettling) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
setIsSettling(true);
|
|
412
|
+
try {
|
|
413
|
+
await request({
|
|
414
|
+
url: `/finance/accounts-payable/installments/${titulo.id}/settlements`,
|
|
415
|
+
method: 'POST',
|
|
416
|
+
data: {
|
|
417
|
+
installment_id: Number(values.installmentId),
|
|
418
|
+
amount: values.amount,
|
|
419
|
+
description: values.description?.trim() || undefined,
|
|
420
|
+
},
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
await refetch();
|
|
424
|
+
setIsSettleDialogOpen(false);
|
|
425
|
+
showToastHandler?.('success', 'Baixa registrada com sucesso');
|
|
426
|
+
} catch (error) {
|
|
427
|
+
showToastHandler?.(
|
|
428
|
+
'error',
|
|
429
|
+
getErrorMessage(error, 'Não foi possível registrar a baixa')
|
|
430
|
+
);
|
|
431
|
+
} finally {
|
|
432
|
+
setIsSettling(false);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const handleReverseSettlement = async (settlementId: string) => {
|
|
437
|
+
if (!settlementId || reversingSettlementId) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
setReversingSettlementId(settlementId);
|
|
442
|
+
try {
|
|
443
|
+
await request({
|
|
444
|
+
url: `/finance/accounts-payable/installments/${titulo.id}/settlements/${settlementId}/reverse`,
|
|
445
|
+
method: 'PATCH',
|
|
446
|
+
data: {},
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
await refetch();
|
|
450
|
+
showToastHandler?.('success', 'Estorno realizado com sucesso');
|
|
451
|
+
} catch (error) {
|
|
452
|
+
showToastHandler?.(
|
|
453
|
+
'error',
|
|
454
|
+
getErrorMessage(error, 'Não foi possível estornar a liquidação')
|
|
455
|
+
);
|
|
456
|
+
} finally {
|
|
457
|
+
setReversingSettlementId(null);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const handleCancel = async () => {
|
|
462
|
+
if (!canCancel || isCanceling) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
setIsCanceling(true);
|
|
467
|
+
try {
|
|
468
|
+
await request({
|
|
469
|
+
url: `/finance/accounts-payable/installments/${titulo.id}/cancel`,
|
|
470
|
+
method: 'PATCH',
|
|
471
|
+
data: {},
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
await refetch();
|
|
475
|
+
setIsCancelDialogOpen(false);
|
|
476
|
+
showToastHandler?.('success', 'Título cancelado com sucesso');
|
|
477
|
+
} catch (error) {
|
|
478
|
+
showToastHandler?.(
|
|
479
|
+
'error',
|
|
480
|
+
getErrorMessage(error, 'Não foi possível cancelar o título')
|
|
481
|
+
);
|
|
482
|
+
} finally {
|
|
483
|
+
setIsCanceling(false);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
270
487
|
return (
|
|
271
488
|
<Page>
|
|
272
489
|
<PageHeader
|
|
@@ -291,29 +508,187 @@ export default function TituloDetalhePage() {
|
|
|
291
508
|
</Button>
|
|
292
509
|
</DropdownMenuTrigger>
|
|
293
510
|
<DropdownMenuContent align="end">
|
|
294
|
-
<DropdownMenuItem
|
|
511
|
+
<DropdownMenuItem
|
|
512
|
+
disabled={!canEdit}
|
|
513
|
+
onClick={() =>
|
|
514
|
+
router.push(
|
|
515
|
+
`/finance/accounts-payable/installments?editId=${titulo.id}`
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
>
|
|
295
519
|
<Edit className="mr-2 h-4 w-4" />
|
|
296
520
|
{t('actions.edit')}
|
|
297
521
|
</DropdownMenuItem>
|
|
298
|
-
<DropdownMenuItem
|
|
522
|
+
<DropdownMenuItem
|
|
523
|
+
disabled={!canApprove || isApproving}
|
|
524
|
+
onClick={() => void handleApprove()}
|
|
525
|
+
>
|
|
299
526
|
<CheckCircle className="mr-2 h-4 w-4" />
|
|
300
527
|
{t('actions.approve')}
|
|
301
528
|
</DropdownMenuItem>
|
|
302
|
-
<DropdownMenuItem
|
|
529
|
+
<DropdownMenuItem
|
|
530
|
+
disabled={!canSettle || settleCandidates.length === 0}
|
|
531
|
+
onClick={() => setIsSettleDialogOpen(true)}
|
|
532
|
+
>
|
|
303
533
|
<Download className="mr-2 h-4 w-4" />
|
|
304
534
|
{t('actions.settle')}
|
|
305
535
|
</DropdownMenuItem>
|
|
306
|
-
<DropdownMenuItem>
|
|
536
|
+
<DropdownMenuItem disabled>
|
|
307
537
|
<Undo className="mr-2 h-4 w-4" />
|
|
308
538
|
{t('actions.reverse')}
|
|
309
539
|
</DropdownMenuItem>
|
|
310
540
|
<DropdownMenuSeparator />
|
|
311
|
-
<DropdownMenuItem
|
|
541
|
+
<DropdownMenuItem
|
|
542
|
+
className="text-destructive"
|
|
543
|
+
disabled={!canCancel || isCanceling}
|
|
544
|
+
onClick={() => setIsCancelDialogOpen(true)}
|
|
545
|
+
>
|
|
312
546
|
<XCircle className="mr-2 h-4 w-4" />
|
|
313
547
|
{t('actions.cancel')}
|
|
314
548
|
</DropdownMenuItem>
|
|
315
549
|
</DropdownMenuContent>
|
|
316
550
|
</DropdownMenu>
|
|
551
|
+
|
|
552
|
+
<AlertDialog
|
|
553
|
+
open={isCancelDialogOpen}
|
|
554
|
+
onOpenChange={setIsCancelDialogOpen}
|
|
555
|
+
>
|
|
556
|
+
<AlertDialogContent>
|
|
557
|
+
<AlertDialogHeader>
|
|
558
|
+
<AlertDialogTitle>Confirmar cancelamento</AlertDialogTitle>
|
|
559
|
+
<AlertDialogDescription>
|
|
560
|
+
Essa ação altera o título para cancelado e não remove os
|
|
561
|
+
registros de auditoria.
|
|
562
|
+
</AlertDialogDescription>
|
|
563
|
+
</AlertDialogHeader>
|
|
564
|
+
<AlertDialogFooter>
|
|
565
|
+
<AlertDialogCancel disabled={isCanceling}>
|
|
566
|
+
Cancelar
|
|
567
|
+
</AlertDialogCancel>
|
|
568
|
+
<AlertDialogAction
|
|
569
|
+
disabled={isCanceling}
|
|
570
|
+
onClick={() => void handleCancel()}
|
|
571
|
+
>
|
|
572
|
+
Confirmar cancelamento
|
|
573
|
+
</AlertDialogAction>
|
|
574
|
+
</AlertDialogFooter>
|
|
575
|
+
</AlertDialogContent>
|
|
576
|
+
</AlertDialog>
|
|
577
|
+
|
|
578
|
+
<Dialog
|
|
579
|
+
open={isSettleDialogOpen}
|
|
580
|
+
onOpenChange={setIsSettleDialogOpen}
|
|
581
|
+
>
|
|
582
|
+
<DialogContent>
|
|
583
|
+
<DialogHeader>
|
|
584
|
+
<DialogTitle>Registrar baixa</DialogTitle>
|
|
585
|
+
<DialogDescription>
|
|
586
|
+
Informe a parcela e o valor da baixa. O backend valida os
|
|
587
|
+
estados e limites de valor automaticamente.
|
|
588
|
+
</DialogDescription>
|
|
589
|
+
</DialogHeader>
|
|
590
|
+
|
|
591
|
+
<Form {...settleForm}>
|
|
592
|
+
<form
|
|
593
|
+
className="space-y-4"
|
|
594
|
+
onSubmit={settleForm.handleSubmit(handleSettle)}
|
|
595
|
+
>
|
|
596
|
+
<FormField
|
|
597
|
+
control={settleForm.control}
|
|
598
|
+
name="installmentId"
|
|
599
|
+
render={({ field }) => (
|
|
600
|
+
<FormItem>
|
|
601
|
+
<FormLabel>Parcela</FormLabel>
|
|
602
|
+
<Select
|
|
603
|
+
value={field.value}
|
|
604
|
+
onValueChange={(value) => {
|
|
605
|
+
field.onChange(value);
|
|
606
|
+
|
|
607
|
+
const selected = settleCandidates.find(
|
|
608
|
+
(parcela: any) => parcela.id === value
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
if (selected) {
|
|
612
|
+
settleForm.setValue(
|
|
613
|
+
'amount',
|
|
614
|
+
Number(selected.valorAberto || 0),
|
|
615
|
+
{ shouldValidate: true }
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
}}
|
|
619
|
+
>
|
|
620
|
+
<FormControl>
|
|
621
|
+
<SelectTrigger>
|
|
622
|
+
<SelectValue placeholder="Selecione" />
|
|
623
|
+
</SelectTrigger>
|
|
624
|
+
</FormControl>
|
|
625
|
+
<SelectContent>
|
|
626
|
+
{settleCandidates.map((parcela: any) => (
|
|
627
|
+
<SelectItem key={parcela.id} value={parcela.id}>
|
|
628
|
+
Parcela {parcela.numero} - em aberto:{' '}
|
|
629
|
+
{new Intl.NumberFormat('pt-BR', {
|
|
630
|
+
style: 'currency',
|
|
631
|
+
currency: 'BRL',
|
|
632
|
+
}).format(Number(parcela.valorAberto || 0))}
|
|
633
|
+
</SelectItem>
|
|
634
|
+
))}
|
|
635
|
+
</SelectContent>
|
|
636
|
+
</Select>
|
|
637
|
+
<FormMessage />
|
|
638
|
+
</FormItem>
|
|
639
|
+
)}
|
|
640
|
+
/>
|
|
641
|
+
|
|
642
|
+
<FormField
|
|
643
|
+
control={settleForm.control}
|
|
644
|
+
name="amount"
|
|
645
|
+
render={({ field }) => (
|
|
646
|
+
<FormItem>
|
|
647
|
+
<FormLabel>Valor</FormLabel>
|
|
648
|
+
<FormControl>
|
|
649
|
+
<InputMoney
|
|
650
|
+
value={Number(field.value || 0)}
|
|
651
|
+
onValueChange={(value) => {
|
|
652
|
+
field.onChange(Number(value || 0));
|
|
653
|
+
}}
|
|
654
|
+
/>
|
|
655
|
+
</FormControl>
|
|
656
|
+
<FormMessage />
|
|
657
|
+
</FormItem>
|
|
658
|
+
)}
|
|
659
|
+
/>
|
|
660
|
+
|
|
661
|
+
<FormField
|
|
662
|
+
control={settleForm.control}
|
|
663
|
+
name="description"
|
|
664
|
+
render={({ field }) => (
|
|
665
|
+
<FormItem>
|
|
666
|
+
<FormLabel>Descrição (opcional)</FormLabel>
|
|
667
|
+
<FormControl>
|
|
668
|
+
<Input {...field} value={field.value || ''} />
|
|
669
|
+
</FormControl>
|
|
670
|
+
<FormMessage />
|
|
671
|
+
</FormItem>
|
|
672
|
+
)}
|
|
673
|
+
/>
|
|
674
|
+
|
|
675
|
+
<DialogFooter>
|
|
676
|
+
<Button
|
|
677
|
+
type="button"
|
|
678
|
+
variant="outline"
|
|
679
|
+
disabled={isSettling}
|
|
680
|
+
onClick={() => setIsSettleDialogOpen(false)}
|
|
681
|
+
>
|
|
682
|
+
Cancelar
|
|
683
|
+
</Button>
|
|
684
|
+
<Button type="submit" disabled={isSettling}>
|
|
685
|
+
Confirmar baixa
|
|
686
|
+
</Button>
|
|
687
|
+
</DialogFooter>
|
|
688
|
+
</form>
|
|
689
|
+
</Form>
|
|
690
|
+
</DialogContent>
|
|
691
|
+
</Dialog>
|
|
317
692
|
</div>
|
|
318
693
|
}
|
|
319
694
|
/>
|
|
@@ -358,6 +733,14 @@ export default function TituloDetalhePage() {
|
|
|
358
733
|
<Money value={titulo.valorTotal} />
|
|
359
734
|
</dd>
|
|
360
735
|
</div>
|
|
736
|
+
<div>
|
|
737
|
+
<dt className="text-sm font-medium text-muted-foreground">
|
|
738
|
+
{t('documentData.status')}
|
|
739
|
+
</dt>
|
|
740
|
+
<dd className="mt-1">
|
|
741
|
+
<StatusBadge status={titulo.status} />
|
|
742
|
+
</dd>
|
|
743
|
+
</div>
|
|
361
744
|
<div>
|
|
362
745
|
<dt className="text-sm font-medium text-muted-foreground">
|
|
363
746
|
{t('documentData.category')}
|
|
@@ -522,6 +905,7 @@ export default function TituloDetalhePage() {
|
|
|
522
905
|
</TableHead>
|
|
523
906
|
<TableHead>{t('settlementsTable.account')}</TableHead>
|
|
524
907
|
<TableHead>{t('settlementsTable.method')}</TableHead>
|
|
908
|
+
<TableHead className="text-right">Ações</TableHead>
|
|
525
909
|
</TableRow>
|
|
526
910
|
</TableHeader>
|
|
527
911
|
<TableBody>
|
|
@@ -547,6 +931,49 @@ export default function TituloDetalhePage() {
|
|
|
547
931
|
<TableCell className="capitalize">
|
|
548
932
|
{liq.metodo}
|
|
549
933
|
</TableCell>
|
|
934
|
+
<TableCell className="text-right">
|
|
935
|
+
<AlertDialog>
|
|
936
|
+
<AlertDialogTrigger asChild>
|
|
937
|
+
<Button
|
|
938
|
+
variant="outline"
|
|
939
|
+
size="sm"
|
|
940
|
+
disabled={
|
|
941
|
+
!liq.settlementId ||
|
|
942
|
+
liq.status === 'reversed' ||
|
|
943
|
+
!!reversingSettlementId
|
|
944
|
+
}
|
|
945
|
+
>
|
|
946
|
+
<Undo className="mr-2 h-4 w-4" />
|
|
947
|
+
Estornar
|
|
948
|
+
</Button>
|
|
949
|
+
</AlertDialogTrigger>
|
|
950
|
+
<AlertDialogContent>
|
|
951
|
+
<AlertDialogHeader>
|
|
952
|
+
<AlertDialogTitle>
|
|
953
|
+
Confirmar estorno
|
|
954
|
+
</AlertDialogTitle>
|
|
955
|
+
<AlertDialogDescription>
|
|
956
|
+
Esta ação cria o estorno da liquidação e
|
|
957
|
+
recalcula saldos e status.
|
|
958
|
+
</AlertDialogDescription>
|
|
959
|
+
</AlertDialogHeader>
|
|
960
|
+
<AlertDialogFooter>
|
|
961
|
+
<AlertDialogCancel>
|
|
962
|
+
Cancelar
|
|
963
|
+
</AlertDialogCancel>
|
|
964
|
+
<AlertDialogAction
|
|
965
|
+
onClick={() =>
|
|
966
|
+
void handleReverseSettlement(
|
|
967
|
+
String(liq.settlementId)
|
|
968
|
+
)
|
|
969
|
+
}
|
|
970
|
+
>
|
|
971
|
+
Confirmar estorno
|
|
972
|
+
</AlertDialogAction>
|
|
973
|
+
</AlertDialogFooter>
|
|
974
|
+
</AlertDialogContent>
|
|
975
|
+
</AlertDialog>
|
|
976
|
+
</TableCell>
|
|
550
977
|
</TableRow>
|
|
551
978
|
);
|
|
552
979
|
})
|