@hed-hog/finance 0.0.278 → 0.0.285
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 +65 -29
- package/dist/dto/finance-report-query.dto.d.ts +16 -0
- package/dist/dto/finance-report-query.dto.d.ts.map +1 -0
- package/dist/dto/finance-report-query.dto.js +59 -0
- package/dist/dto/finance-report-query.dto.js.map +1 -0
- package/dist/finance-reports.controller.d.ts +71 -0
- package/dist/finance-reports.controller.d.ts.map +1 -0
- package/dist/finance-reports.controller.js +61 -0
- package/dist/finance-reports.controller.js.map +1 -0
- package/dist/finance.module.d.ts.map +1 -1
- package/dist/finance.module.js +2 -0
- package/dist/finance.module.js.map +1 -1
- package/dist/finance.service.d.ts +93 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +456 -0
- package/dist/finance.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/hedhog/data/menu.yaml +46 -0
- package/hedhog/data/route.yaml +27 -0
- package/hedhog/frontend/app/accounts-payable/installments/page.tsx.ejs +158 -125
- package/hedhog/frontend/app/accounts-receivable/collections-default/page.tsx.ejs +102 -88
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +113 -89
- package/hedhog/frontend/app/reports/_lib/report-aggregations.ts.ejs +275 -0
- package/hedhog/frontend/app/reports/_lib/report-mocks.ts.ejs +186 -0
- package/hedhog/frontend/app/reports/_lib/use-finance-reports.ts.ejs +233 -0
- package/hedhog/frontend/app/reports/overview-results/page.tsx.ejs +355 -0
- package/hedhog/frontend/app/reports/top-customers/page.tsx.ejs +427 -0
- package/hedhog/frontend/app/reports/top-operational-expenses/page.tsx.ejs +433 -0
- package/hedhog/frontend/messages/en.json +179 -0
- package/hedhog/frontend/messages/pt.json +179 -0
- package/package.json +7 -7
- package/src/dto/finance-report-query.dto.ts +49 -0
- package/src/finance-reports.controller.ts +28 -0
- package/src/finance.module.ts +2 -0
- package/src/finance.service.ts +645 -10
- package/src/index.ts +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Page, PageHeader } from '@/components/entity-list';
|
|
3
|
+
import { EmptyState, Page, PageHeader } from '@/components/entity-list';
|
|
4
4
|
import { RichTextEditor } from '@/components/rich-text-editor';
|
|
5
5
|
import { Button } from '@/components/ui/button';
|
|
6
6
|
import {
|
|
@@ -548,94 +548,108 @@ export default function CobrancaPage() {
|
|
|
548
548
|
<CardDescription>{t('table.description')}</CardDescription>
|
|
549
549
|
</CardHeader>
|
|
550
550
|
<CardContent>
|
|
551
|
-
|
|
552
|
-
<
|
|
553
|
-
<
|
|
554
|
-
<
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
</TableHeader>
|
|
575
|
-
<TableBody>
|
|
576
|
-
{agingInadimplencia.map((item) => (
|
|
577
|
-
<TableRow key={item.clienteId}>
|
|
578
|
-
<TableCell className="font-medium">{item.cliente}</TableCell>
|
|
579
|
-
<TableCell className="text-right">
|
|
580
|
-
{item.bucket0_30 > 0 ? (
|
|
581
|
-
<Money value={item.bucket0_30} />
|
|
582
|
-
) : (
|
|
583
|
-
'-'
|
|
584
|
-
)}
|
|
585
|
-
</TableCell>
|
|
586
|
-
<TableCell className="text-right">
|
|
587
|
-
{item.bucket31_60 > 0 ? (
|
|
588
|
-
<Money value={item.bucket31_60} />
|
|
589
|
-
) : (
|
|
590
|
-
'-'
|
|
591
|
-
)}
|
|
592
|
-
</TableCell>
|
|
593
|
-
<TableCell className="text-right">
|
|
594
|
-
{item.bucket61_90 > 0 ? (
|
|
595
|
-
<Money value={item.bucket61_90} />
|
|
596
|
-
) : (
|
|
597
|
-
'-'
|
|
598
|
-
)}
|
|
599
|
-
</TableCell>
|
|
600
|
-
<TableCell className="text-right">
|
|
601
|
-
{item.bucket90plus > 0 ? (
|
|
602
|
-
<span className="font-medium text-destructive">
|
|
603
|
-
<Money value={item.bucket90plus} />
|
|
604
|
-
</span>
|
|
605
|
-
) : (
|
|
606
|
-
'-'
|
|
607
|
-
)}
|
|
608
|
-
</TableCell>
|
|
609
|
-
<TableCell className="text-right font-semibold">
|
|
610
|
-
<Money value={item.total} />
|
|
611
|
-
</TableCell>
|
|
612
|
-
<TableCell>
|
|
613
|
-
<div className="flex justify-end gap-2">
|
|
614
|
-
<HistoricoContatosSheet
|
|
615
|
-
cliente={item.cliente}
|
|
616
|
-
historicoContatos={historicoContatos.filter(
|
|
617
|
-
(contato) => contato.clienteId === item.clienteId
|
|
618
|
-
)}
|
|
619
|
-
t={t}
|
|
620
|
-
/>
|
|
621
|
-
<EnviarCobrancaDialog
|
|
622
|
-
cliente={item.cliente}
|
|
623
|
-
clienteId={item.clienteId}
|
|
624
|
-
onSuccess={refetch}
|
|
625
|
-
t={t}
|
|
626
|
-
/>
|
|
627
|
-
<RegistrarAcordoDialog
|
|
628
|
-
cliente={item.cliente}
|
|
629
|
-
clienteId={item.clienteId}
|
|
630
|
-
onSuccess={refetch}
|
|
631
|
-
t={t}
|
|
632
|
-
/>
|
|
633
|
-
</div>
|
|
634
|
-
</TableCell>
|
|
551
|
+
{agingInadimplencia.length > 0 ? (
|
|
552
|
+
<Table>
|
|
553
|
+
<TableHeader>
|
|
554
|
+
<TableRow>
|
|
555
|
+
<TableHead>{t('table.headers.client')}</TableHead>
|
|
556
|
+
<TableHead className="text-right">
|
|
557
|
+
{t('table.headers.range0to30')}
|
|
558
|
+
</TableHead>
|
|
559
|
+
<TableHead className="text-right">
|
|
560
|
+
{t('table.headers.range31to60')}
|
|
561
|
+
</TableHead>
|
|
562
|
+
<TableHead className="text-right">
|
|
563
|
+
{t('table.headers.range61to90')}
|
|
564
|
+
</TableHead>
|
|
565
|
+
<TableHead className="text-right">
|
|
566
|
+
{t('table.headers.range90plus')}
|
|
567
|
+
</TableHead>
|
|
568
|
+
<TableHead className="text-right">
|
|
569
|
+
{t('table.headers.total')}
|
|
570
|
+
</TableHead>
|
|
571
|
+
<TableHead className="text-right">
|
|
572
|
+
{t('table.headers.actions')}
|
|
573
|
+
</TableHead>
|
|
635
574
|
</TableRow>
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
575
|
+
</TableHeader>
|
|
576
|
+
<TableBody>
|
|
577
|
+
{agingInadimplencia.map((item) => (
|
|
578
|
+
<TableRow key={item.clienteId}>
|
|
579
|
+
<TableCell className="font-medium">
|
|
580
|
+
{item.cliente}
|
|
581
|
+
</TableCell>
|
|
582
|
+
<TableCell className="text-right">
|
|
583
|
+
{item.bucket0_30 > 0 ? (
|
|
584
|
+
<Money value={item.bucket0_30} />
|
|
585
|
+
) : (
|
|
586
|
+
'-'
|
|
587
|
+
)}
|
|
588
|
+
</TableCell>
|
|
589
|
+
<TableCell className="text-right">
|
|
590
|
+
{item.bucket31_60 > 0 ? (
|
|
591
|
+
<Money value={item.bucket31_60} />
|
|
592
|
+
) : (
|
|
593
|
+
'-'
|
|
594
|
+
)}
|
|
595
|
+
</TableCell>
|
|
596
|
+
<TableCell className="text-right">
|
|
597
|
+
{item.bucket61_90 > 0 ? (
|
|
598
|
+
<Money value={item.bucket61_90} />
|
|
599
|
+
) : (
|
|
600
|
+
'-'
|
|
601
|
+
)}
|
|
602
|
+
</TableCell>
|
|
603
|
+
<TableCell className="text-right">
|
|
604
|
+
{item.bucket90plus > 0 ? (
|
|
605
|
+
<span className="font-medium text-destructive">
|
|
606
|
+
<Money value={item.bucket90plus} />
|
|
607
|
+
</span>
|
|
608
|
+
) : (
|
|
609
|
+
'-'
|
|
610
|
+
)}
|
|
611
|
+
</TableCell>
|
|
612
|
+
<TableCell className="text-right font-semibold">
|
|
613
|
+
<Money value={item.total} />
|
|
614
|
+
</TableCell>
|
|
615
|
+
<TableCell>
|
|
616
|
+
<div className="flex justify-end gap-2">
|
|
617
|
+
<HistoricoContatosSheet
|
|
618
|
+
cliente={item.cliente}
|
|
619
|
+
historicoContatos={historicoContatos.filter(
|
|
620
|
+
(contato) => contato.clienteId === item.clienteId
|
|
621
|
+
)}
|
|
622
|
+
t={t}
|
|
623
|
+
/>
|
|
624
|
+
<EnviarCobrancaDialog
|
|
625
|
+
cliente={item.cliente}
|
|
626
|
+
clienteId={item.clienteId}
|
|
627
|
+
onSuccess={refetch}
|
|
628
|
+
t={t}
|
|
629
|
+
/>
|
|
630
|
+
<RegistrarAcordoDialog
|
|
631
|
+
cliente={item.cliente}
|
|
632
|
+
clienteId={item.clienteId}
|
|
633
|
+
onSuccess={refetch}
|
|
634
|
+
t={t}
|
|
635
|
+
/>
|
|
636
|
+
</div>
|
|
637
|
+
</TableCell>
|
|
638
|
+
</TableRow>
|
|
639
|
+
))}
|
|
640
|
+
</TableBody>
|
|
641
|
+
</Table>
|
|
642
|
+
) : (
|
|
643
|
+
<EmptyState
|
|
644
|
+
icon={<FileText className="h-12 w-12" />}
|
|
645
|
+
title={t('empty.title')}
|
|
646
|
+
description={t('empty.description')}
|
|
647
|
+
actionLabel={t('empty.action')}
|
|
648
|
+
onAction={() => {
|
|
649
|
+
void refetch();
|
|
650
|
+
}}
|
|
651
|
+
/>
|
|
652
|
+
)}
|
|
639
653
|
</CardContent>
|
|
640
654
|
</Card>
|
|
641
655
|
</Page>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Page, PageHeader } from '@/components/entity-list';
|
|
3
|
+
import { EmptyState, Page, PageHeader } from '@/components/entity-list';
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
5
|
import {
|
|
6
6
|
Card,
|
|
@@ -357,16 +357,27 @@ function ImportarExtratoSheet({
|
|
|
357
357
|
defaultBankAccountId,
|
|
358
358
|
onImported,
|
|
359
359
|
onBankAccountCreated,
|
|
360
|
+
open,
|
|
361
|
+
onOpenChange,
|
|
360
362
|
}: {
|
|
361
363
|
contasBancarias: BankAccount[];
|
|
362
364
|
t: ReturnType<typeof useTranslations>;
|
|
363
365
|
defaultBankAccountId?: string;
|
|
364
366
|
onImported: () => Promise<any> | void;
|
|
365
367
|
onBankAccountCreated: (createdBankAccountId?: string) => Promise<void> | void;
|
|
368
|
+
open?: boolean;
|
|
369
|
+
onOpenChange?: (open: boolean) => void;
|
|
366
370
|
}) {
|
|
367
371
|
const { request, showToastHandler } = useApp();
|
|
368
|
-
const [
|
|
372
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
369
373
|
const [openNovaContaSheet, setOpenNovaContaSheet] = useState(false);
|
|
374
|
+
const isOpen = open ?? internalOpen;
|
|
375
|
+
const handleOpenChange = (nextOpen: boolean) => {
|
|
376
|
+
onOpenChange?.(nextOpen);
|
|
377
|
+
if (open === undefined) {
|
|
378
|
+
setInternalOpen(nextOpen);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
370
381
|
|
|
371
382
|
const form = useForm<ImportStatementFormValues>({
|
|
372
383
|
resolver: zodResolver(importStatementSchema),
|
|
@@ -376,7 +387,7 @@ function ImportarExtratoSheet({
|
|
|
376
387
|
});
|
|
377
388
|
|
|
378
389
|
useEffect(() => {
|
|
379
|
-
if (!
|
|
390
|
+
if (!isOpen) {
|
|
380
391
|
return;
|
|
381
392
|
}
|
|
382
393
|
|
|
@@ -384,7 +395,7 @@ function ImportarExtratoSheet({
|
|
|
384
395
|
bankAccountId: defaultBankAccountId || '',
|
|
385
396
|
file: undefined as unknown as File,
|
|
386
397
|
});
|
|
387
|
-
}, [defaultBankAccountId, form,
|
|
398
|
+
}, [defaultBankAccountId, form, isOpen]);
|
|
388
399
|
|
|
389
400
|
const handleSubmit = async (values: ImportStatementFormValues) => {
|
|
390
401
|
const formData = new FormData();
|
|
@@ -400,15 +411,15 @@ function ImportarExtratoSheet({
|
|
|
400
411
|
|
|
401
412
|
await onImported();
|
|
402
413
|
showToastHandler?.('success', 'Extrato importado com sucesso');
|
|
403
|
-
|
|
414
|
+
handleOpenChange(false);
|
|
404
415
|
} catch {
|
|
405
416
|
showToastHandler?.('error', 'Não foi possível importar o extrato');
|
|
406
417
|
}
|
|
407
418
|
};
|
|
408
419
|
|
|
409
420
|
return (
|
|
410
|
-
<Sheet open={
|
|
411
|
-
<Button onClick={() =>
|
|
421
|
+
<Sheet open={isOpen} onOpenChange={handleOpenChange}>
|
|
422
|
+
<Button onClick={() => handleOpenChange(true)}>
|
|
412
423
|
<Upload className="mr-2 h-4 w-4" />
|
|
413
424
|
{t('importDialog.action')}
|
|
414
425
|
</Button>
|
|
@@ -510,7 +521,7 @@ function ImportarExtratoSheet({
|
|
|
510
521
|
<Button
|
|
511
522
|
type="button"
|
|
512
523
|
variant="outline"
|
|
513
|
-
onClick={() =>
|
|
524
|
+
onClick={() => handleOpenChange(false)}
|
|
514
525
|
>
|
|
515
526
|
{t('common.cancel')}
|
|
516
527
|
</Button>
|
|
@@ -536,6 +547,7 @@ export default function ExtratosPage() {
|
|
|
536
547
|
const [contaFilter, setContaFilter] = useState<string>('');
|
|
537
548
|
const [search, setSearch] = useState('');
|
|
538
549
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
550
|
+
const [isImportSheetOpen, setIsImportSheetOpen] = useState(false);
|
|
539
551
|
const [extratoSelecionado, setExtratoSelecionado] =
|
|
540
552
|
useState<Statement | null>(null);
|
|
541
553
|
|
|
@@ -717,6 +729,8 @@ export default function ExtratosPage() {
|
|
|
717
729
|
contasBancarias={contasBancarias}
|
|
718
730
|
t={t}
|
|
719
731
|
defaultBankAccountId={contaFilter}
|
|
732
|
+
open={isImportSheetOpen}
|
|
733
|
+
onOpenChange={setIsImportSheetOpen}
|
|
720
734
|
onImported={refetchExtratos}
|
|
721
735
|
onBankAccountCreated={handleBankAccountCreated}
|
|
722
736
|
/>
|
|
@@ -797,89 +811,99 @@ export default function ExtratosPage() {
|
|
|
797
811
|
</Card>
|
|
798
812
|
</div>
|
|
799
813
|
|
|
800
|
-
|
|
801
|
-
<
|
|
802
|
-
<
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
<
|
|
809
|
-
<
|
|
810
|
-
<
|
|
811
|
-
<
|
|
812
|
-
<
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
event.
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}}
|
|
839
|
-
role="button"
|
|
840
|
-
tabIndex={0}
|
|
841
|
-
>
|
|
842
|
-
<TableCell>{formatarData(extrato.data)}</TableCell>
|
|
843
|
-
<TableCell className="truncate" title={extrato.descricao}>
|
|
844
|
-
{extrato.descricao}
|
|
845
|
-
</TableCell>
|
|
846
|
-
<TableCell className="text-right">
|
|
847
|
-
<span
|
|
848
|
-
className={
|
|
849
|
-
extrato.tipo === 'entrada'
|
|
850
|
-
? 'text-green-600'
|
|
851
|
-
: 'text-red-600'
|
|
814
|
+
{extratos.length > 0 ? (
|
|
815
|
+
<Card>
|
|
816
|
+
<CardHeader>
|
|
817
|
+
<CardTitle>{t('table.title')}</CardTitle>
|
|
818
|
+
<CardDescription>
|
|
819
|
+
{t('table.foundTransactions', { count: extratos.length })}
|
|
820
|
+
</CardDescription>
|
|
821
|
+
</CardHeader>
|
|
822
|
+
<CardContent>
|
|
823
|
+
<div className="overflow-x-auto">
|
|
824
|
+
<Table className="min-w-[760px] table-fixed">
|
|
825
|
+
<TableHeader>
|
|
826
|
+
<TableRow>
|
|
827
|
+
<TableHead className="w-[110px]">
|
|
828
|
+
{t('table.headers.date')}
|
|
829
|
+
</TableHead>
|
|
830
|
+
<TableHead>{t('table.headers.description')}</TableHead>
|
|
831
|
+
<TableHead className="w-[130px] text-right">
|
|
832
|
+
{t('table.headers.value')}
|
|
833
|
+
</TableHead>
|
|
834
|
+
<TableHead className="w-[110px]">
|
|
835
|
+
{t('table.headers.type')}
|
|
836
|
+
</TableHead>
|
|
837
|
+
<TableHead className="w-[140px]">
|
|
838
|
+
{t('table.headers.reconciliation')}
|
|
839
|
+
</TableHead>
|
|
840
|
+
</TableRow>
|
|
841
|
+
</TableHeader>
|
|
842
|
+
<TableBody>
|
|
843
|
+
{extratos.map((extrato) => (
|
|
844
|
+
<TableRow
|
|
845
|
+
key={extrato.id}
|
|
846
|
+
className="cursor-pointer"
|
|
847
|
+
onClick={() => setExtratoSelecionado(extrato)}
|
|
848
|
+
onKeyDown={(event) => {
|
|
849
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
850
|
+
event.preventDefault();
|
|
851
|
+
setExtratoSelecionado(extrato);
|
|
852
852
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
{extrato.
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
853
|
+
}}
|
|
854
|
+
role="button"
|
|
855
|
+
tabIndex={0}
|
|
856
|
+
>
|
|
857
|
+
<TableCell>{formatarData(extrato.data)}</TableCell>
|
|
858
|
+
<TableCell className="truncate" title={extrato.descricao}>
|
|
859
|
+
{extrato.descricao}
|
|
860
|
+
</TableCell>
|
|
861
|
+
<TableCell className="text-right">
|
|
862
|
+
<span
|
|
863
|
+
className={
|
|
864
|
+
extrato.tipo === 'entrada'
|
|
865
|
+
? 'text-green-600'
|
|
866
|
+
: 'text-red-600'
|
|
867
|
+
}
|
|
868
|
+
>
|
|
869
|
+
<Money value={extrato.valor} />
|
|
867
870
|
</span>
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
871
|
+
</TableCell>
|
|
872
|
+
<TableCell>
|
|
873
|
+
{extrato.tipo === 'entrada' ? (
|
|
874
|
+
<span className="flex items-center gap-1 text-green-600">
|
|
875
|
+
<ArrowUpRight className="h-4 w-4" />
|
|
876
|
+
{t('types.inflow')}
|
|
877
|
+
</span>
|
|
878
|
+
) : (
|
|
879
|
+
<span className="flex items-center gap-1 text-red-600">
|
|
880
|
+
<ArrowDownRight className="h-4 w-4" />
|
|
881
|
+
{t('types.outflow')}
|
|
882
|
+
</span>
|
|
883
|
+
)}
|
|
884
|
+
</TableCell>
|
|
885
|
+
<TableCell>
|
|
886
|
+
<StatusBadge
|
|
887
|
+
status={extrato.statusConciliacao}
|
|
888
|
+
type="conciliacao"
|
|
889
|
+
/>
|
|
890
|
+
</TableCell>
|
|
891
|
+
</TableRow>
|
|
892
|
+
))}
|
|
893
|
+
</TableBody>
|
|
894
|
+
</Table>
|
|
895
|
+
</div>
|
|
896
|
+
</CardContent>
|
|
897
|
+
</Card>
|
|
898
|
+
) : (
|
|
899
|
+
<EmptyState
|
|
900
|
+
icon={<Upload className="h-12 w-12" />}
|
|
901
|
+
title={t('empty.title')}
|
|
902
|
+
description={t('empty.description')}
|
|
903
|
+
actionLabel={t('importDialog.action')}
|
|
904
|
+
onAction={() => setIsImportSheetOpen(true)}
|
|
905
|
+
/>
|
|
906
|
+
)}
|
|
883
907
|
|
|
884
908
|
<Dialog
|
|
885
909
|
open={!!extratoSelecionado}
|