@hed-hog/finance 0.0.246 → 0.0.250
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-data.controller.d.ts +4 -0
- package/dist/finance-data.controller.d.ts.map +1 -1
- package/dist/finance.service.d.ts +10 -0
- package/dist/finance.service.d.ts.map +1 -1
- package/dist/finance.service.js +77 -9
- package/dist/finance.service.js.map +1 -1
- package/hedhog/frontend/app/_lib/use-finance-data.ts.ejs +5 -0
- package/hedhog/frontend/app/cash-and-banks/statements/page.tsx.ejs +192 -55
- package/hedhog/frontend/app/page.tsx.ejs +19 -3
- package/package.json +5 -5
- package/src/finance.service.ts +121 -8
|
@@ -9,6 +9,14 @@ import {
|
|
|
9
9
|
CardHeader,
|
|
10
10
|
CardTitle,
|
|
11
11
|
} from '@/components/ui/card';
|
|
12
|
+
import {
|
|
13
|
+
Dialog,
|
|
14
|
+
DialogContent,
|
|
15
|
+
DialogDescription,
|
|
16
|
+
DialogFooter,
|
|
17
|
+
DialogHeader,
|
|
18
|
+
DialogTitle,
|
|
19
|
+
} from '@/components/ui/dialog';
|
|
12
20
|
import { FilterBar } from '@/components/ui/filter-bar';
|
|
13
21
|
import {
|
|
14
22
|
Form,
|
|
@@ -166,7 +174,7 @@ function ImportarExtratoSheet({
|
|
|
166
174
|
<FormLabel>{t('importDialog.bankAccount')}</FormLabel>
|
|
167
175
|
<Select value={field.value} onValueChange={field.onChange}>
|
|
168
176
|
<FormControl>
|
|
169
|
-
<SelectTrigger>
|
|
177
|
+
<SelectTrigger className="w-full">
|
|
170
178
|
<SelectValue
|
|
171
179
|
placeholder={t('importDialog.selectAccount')}
|
|
172
180
|
/>
|
|
@@ -238,8 +246,10 @@ export default function ExtratosPage() {
|
|
|
238
246
|
|
|
239
247
|
const [contaFilter, setContaFilter] = useState<string>('');
|
|
240
248
|
const [search, setSearch] = useState('');
|
|
249
|
+
const [extratoSelecionado, setExtratoSelecionado] =
|
|
250
|
+
useState<Statement | null>(null);
|
|
241
251
|
|
|
242
|
-
const { data: contasBancarias } = useQuery<BankAccount[]>({
|
|
252
|
+
const { data: contasBancarias = [] } = useQuery<BankAccount[]>({
|
|
243
253
|
queryKey: ['finance-bank-accounts'],
|
|
244
254
|
queryFn: async () => {
|
|
245
255
|
const response = await request({
|
|
@@ -249,7 +259,6 @@ export default function ExtratosPage() {
|
|
|
249
259
|
|
|
250
260
|
return (response?.data || []) as BankAccount[];
|
|
251
261
|
},
|
|
252
|
-
initialData: [],
|
|
253
262
|
});
|
|
254
263
|
|
|
255
264
|
useEffect(() => {
|
|
@@ -285,7 +294,9 @@ export default function ExtratosPage() {
|
|
|
285
294
|
searchParams,
|
|
286
295
|
]);
|
|
287
296
|
|
|
288
|
-
const { data: extratos, refetch: refetchExtratos } = useQuery<
|
|
297
|
+
const { data: extratos = [], refetch: refetchExtratos } = useQuery<
|
|
298
|
+
Statement[]
|
|
299
|
+
>({
|
|
289
300
|
queryKey: ['finance-statements', contaFilter],
|
|
290
301
|
queryFn: async () => {
|
|
291
302
|
if (!contaFilter) {
|
|
@@ -299,7 +310,6 @@ export default function ExtratosPage() {
|
|
|
299
310
|
|
|
300
311
|
return (response?.data || []) as Statement[];
|
|
301
312
|
},
|
|
302
|
-
initialData: [],
|
|
303
313
|
});
|
|
304
314
|
|
|
305
315
|
const filteredExtratos = useMemo(
|
|
@@ -311,6 +321,11 @@ export default function ExtratosPage() {
|
|
|
311
321
|
);
|
|
312
322
|
|
|
313
323
|
const conta = contasBancarias.find((item) => item.id === contaFilter);
|
|
324
|
+
const contaExtratoSelecionado = extratoSelecionado
|
|
325
|
+
? contasBancarias.find(
|
|
326
|
+
(item) => item.id === extratoSelecionado.contaBancariaId
|
|
327
|
+
)
|
|
328
|
+
: undefined;
|
|
314
329
|
const totalEntradas = filteredExtratos
|
|
315
330
|
.filter((e) => e.tipo === 'entrada')
|
|
316
331
|
.reduce((acc, e) => acc + e.valor, 0);
|
|
@@ -466,60 +481,182 @@ export default function ExtratosPage() {
|
|
|
466
481
|
</CardDescription>
|
|
467
482
|
</CardHeader>
|
|
468
483
|
<CardContent>
|
|
469
|
-
<
|
|
470
|
-
<
|
|
471
|
-
<
|
|
472
|
-
<
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
484
|
+
<div className="overflow-x-auto">
|
|
485
|
+
<Table className="min-w-[760px] table-fixed">
|
|
486
|
+
<TableHeader>
|
|
487
|
+
<TableRow>
|
|
488
|
+
<TableHead className="w-[110px]">
|
|
489
|
+
{t('table.headers.date')}
|
|
490
|
+
</TableHead>
|
|
491
|
+
<TableHead>{t('table.headers.description')}</TableHead>
|
|
492
|
+
<TableHead className="w-[130px] text-right">
|
|
493
|
+
{t('table.headers.value')}
|
|
494
|
+
</TableHead>
|
|
495
|
+
<TableHead className="w-[110px]">
|
|
496
|
+
{t('table.headers.type')}
|
|
497
|
+
</TableHead>
|
|
498
|
+
<TableHead className="w-[140px]">
|
|
499
|
+
{t('table.headers.reconciliation')}
|
|
500
|
+
</TableHead>
|
|
501
|
+
</TableRow>
|
|
502
|
+
</TableHeader>
|
|
503
|
+
<TableBody>
|
|
504
|
+
{filteredExtratos.map((extrato) => (
|
|
505
|
+
<TableRow
|
|
506
|
+
key={extrato.id}
|
|
507
|
+
className="cursor-pointer"
|
|
508
|
+
onClick={() => setExtratoSelecionado(extrato)}
|
|
509
|
+
onKeyDown={(event) => {
|
|
510
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
511
|
+
event.preventDefault();
|
|
512
|
+
setExtratoSelecionado(extrato);
|
|
492
513
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
514
|
+
}}
|
|
515
|
+
role="button"
|
|
516
|
+
tabIndex={0}
|
|
517
|
+
>
|
|
518
|
+
<TableCell>{formatarData(extrato.data)}</TableCell>
|
|
519
|
+
<TableCell className="truncate" title={extrato.descricao}>
|
|
520
|
+
{extrato.descricao}
|
|
521
|
+
</TableCell>
|
|
522
|
+
<TableCell className="text-right">
|
|
523
|
+
<span
|
|
524
|
+
className={
|
|
525
|
+
extrato.tipo === 'entrada'
|
|
526
|
+
? 'text-green-600'
|
|
527
|
+
: 'text-red-600'
|
|
528
|
+
}
|
|
529
|
+
>
|
|
530
|
+
<Money value={extrato.valor} />
|
|
503
531
|
</span>
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
532
|
+
</TableCell>
|
|
533
|
+
<TableCell>
|
|
534
|
+
{extrato.tipo === 'entrada' ? (
|
|
535
|
+
<span className="flex items-center gap-1 text-green-600">
|
|
536
|
+
<ArrowUpRight className="h-4 w-4" />
|
|
537
|
+
{t('types.inflow')}
|
|
538
|
+
</span>
|
|
539
|
+
) : (
|
|
540
|
+
<span className="flex items-center gap-1 text-red-600">
|
|
541
|
+
<ArrowDownRight className="h-4 w-4" />
|
|
542
|
+
{t('types.outflow')}
|
|
543
|
+
</span>
|
|
544
|
+
)}
|
|
545
|
+
</TableCell>
|
|
546
|
+
<TableCell>
|
|
547
|
+
<StatusBadge
|
|
548
|
+
status={extrato.statusConciliacao}
|
|
549
|
+
type="conciliacao"
|
|
550
|
+
/>
|
|
551
|
+
</TableCell>
|
|
552
|
+
</TableRow>
|
|
553
|
+
))}
|
|
554
|
+
</TableBody>
|
|
555
|
+
</Table>
|
|
556
|
+
</div>
|
|
521
557
|
</CardContent>
|
|
522
558
|
</Card>
|
|
559
|
+
|
|
560
|
+
<Dialog
|
|
561
|
+
open={!!extratoSelecionado}
|
|
562
|
+
onOpenChange={(open) => {
|
|
563
|
+
if (!open) {
|
|
564
|
+
setExtratoSelecionado(null);
|
|
565
|
+
}
|
|
566
|
+
}}
|
|
567
|
+
>
|
|
568
|
+
<DialogContent className="sm:max-w-lg">
|
|
569
|
+
<DialogHeader>
|
|
570
|
+
<DialogTitle>{t('table.title')}</DialogTitle>
|
|
571
|
+
<DialogDescription>{t('header.description')}</DialogDescription>
|
|
572
|
+
</DialogHeader>
|
|
573
|
+
|
|
574
|
+
{extratoSelecionado ? (
|
|
575
|
+
<div className="space-y-4 rounded-md border p-4">
|
|
576
|
+
<div className="text-center">
|
|
577
|
+
<p className="text-sm text-muted-foreground">
|
|
578
|
+
{t('table.headers.reconciliation')}
|
|
579
|
+
</p>
|
|
580
|
+
<div className="mt-1 flex justify-center">
|
|
581
|
+
<StatusBadge
|
|
582
|
+
status={extratoSelecionado.statusConciliacao}
|
|
583
|
+
type="conciliacao"
|
|
584
|
+
/>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
589
|
+
<div>
|
|
590
|
+
<p className="text-xs text-muted-foreground">
|
|
591
|
+
{t('table.headers.date')}
|
|
592
|
+
</p>
|
|
593
|
+
<p className="font-medium">
|
|
594
|
+
{formatarData(extratoSelecionado.data)}
|
|
595
|
+
</p>
|
|
596
|
+
</div>
|
|
597
|
+
<div>
|
|
598
|
+
<p className="text-xs text-muted-foreground">
|
|
599
|
+
{t('table.headers.type')}
|
|
600
|
+
</p>
|
|
601
|
+
<p className="font-medium">
|
|
602
|
+
{extratoSelecionado.tipo === 'entrada'
|
|
603
|
+
? t('types.inflow')
|
|
604
|
+
: t('types.outflow')}
|
|
605
|
+
</p>
|
|
606
|
+
</div>
|
|
607
|
+
<div className="sm:col-span-2">
|
|
608
|
+
<p className="text-xs text-muted-foreground">
|
|
609
|
+
{t('table.headers.description')}
|
|
610
|
+
</p>
|
|
611
|
+
<p className="font-medium wrap-break-word">
|
|
612
|
+
{extratoSelecionado.descricao}
|
|
613
|
+
</p>
|
|
614
|
+
</div>
|
|
615
|
+
<div>
|
|
616
|
+
<p className="text-xs text-muted-foreground">
|
|
617
|
+
{t('table.headers.value')}
|
|
618
|
+
</p>
|
|
619
|
+
<p
|
|
620
|
+
className={`font-medium ${
|
|
621
|
+
extratoSelecionado.tipo === 'entrada'
|
|
622
|
+
? 'text-green-600'
|
|
623
|
+
: 'text-red-600'
|
|
624
|
+
}`}
|
|
625
|
+
>
|
|
626
|
+
<Money value={extratoSelecionado.valor} />
|
|
627
|
+
</p>
|
|
628
|
+
</div>
|
|
629
|
+
<div>
|
|
630
|
+
<p className="text-xs text-muted-foreground">
|
|
631
|
+
{t('importDialog.bankAccount')}
|
|
632
|
+
</p>
|
|
633
|
+
<p className="font-medium">
|
|
634
|
+
{contaExtratoSelecionado
|
|
635
|
+
? `${contaExtratoSelecionado.banco} - ${contaExtratoSelecionado.descricao}`
|
|
636
|
+
: '-'}
|
|
637
|
+
</p>
|
|
638
|
+
</div>
|
|
639
|
+
<div className="sm:col-span-2">
|
|
640
|
+
<p className="text-xs text-muted-foreground">ID</p>
|
|
641
|
+
<p className="font-mono text-xs break-all">
|
|
642
|
+
{extratoSelecionado.id}
|
|
643
|
+
</p>
|
|
644
|
+
</div>
|
|
645
|
+
</div>
|
|
646
|
+
</div>
|
|
647
|
+
) : null}
|
|
648
|
+
|
|
649
|
+
<DialogFooter>
|
|
650
|
+
<Button
|
|
651
|
+
type="button"
|
|
652
|
+
variant="outline"
|
|
653
|
+
onClick={() => setExtratoSelecionado(null)}
|
|
654
|
+
>
|
|
655
|
+
{t('common.cancel')}
|
|
656
|
+
</Button>
|
|
657
|
+
</DialogFooter>
|
|
658
|
+
</DialogContent>
|
|
659
|
+
</Dialog>
|
|
523
660
|
</Page>
|
|
524
661
|
);
|
|
525
662
|
}
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
TrendingUp,
|
|
21
21
|
Wallet,
|
|
22
22
|
} from 'lucide-react';
|
|
23
|
-
import { useTranslations } from 'next-intl';
|
|
23
|
+
import { useLocale, useTranslations } from 'next-intl';
|
|
24
24
|
import {
|
|
25
25
|
CartesianGrid,
|
|
26
26
|
Legend,
|
|
@@ -204,10 +204,14 @@ function ProximosVencimentos({
|
|
|
204
204
|
function Alertas({
|
|
205
205
|
titulosPagar,
|
|
206
206
|
extratos,
|
|
207
|
+
periodoAberto,
|
|
208
|
+
locale,
|
|
207
209
|
t,
|
|
208
210
|
}: {
|
|
209
211
|
titulosPagar: any[];
|
|
210
212
|
extratos: any[];
|
|
213
|
+
periodoAberto?: { inicio?: string | null } | null;
|
|
214
|
+
locale: string;
|
|
211
215
|
t: ReturnType<typeof useTranslations>;
|
|
212
216
|
}) {
|
|
213
217
|
const vencidos = titulosPagar.filter((t) =>
|
|
@@ -217,6 +221,10 @@ function Alertas({
|
|
|
217
221
|
(e) => e.statusConciliacao === 'pendente'
|
|
218
222
|
).length;
|
|
219
223
|
|
|
224
|
+
const periodoBase = periodoAberto?.inicio ? new Date(periodoAberto.inicio) : new Date();
|
|
225
|
+
const mes = new Intl.DateTimeFormat(locale, { month: 'long' }).format(periodoBase);
|
|
226
|
+
const periodoAtual = `${mes.charAt(0).toUpperCase()}${mes.slice(1)}/${periodoBase.getFullYear()}`;
|
|
227
|
+
|
|
220
228
|
return (
|
|
221
229
|
<Card>
|
|
222
230
|
<CardHeader>
|
|
@@ -249,7 +257,7 @@ function Alertas({
|
|
|
249
257
|
<div className="flex items-center justify-between rounded-lg bg-blue-50 p-3">
|
|
250
258
|
<span className="text-sm">{t('alerts.openPeriod')}</span>
|
|
251
259
|
<Badge variant="outline" className="border-blue-500 text-blue-700">
|
|
252
|
-
{
|
|
260
|
+
{periodoAtual}
|
|
253
261
|
</Badge>
|
|
254
262
|
</div>
|
|
255
263
|
</div>
|
|
@@ -260,6 +268,7 @@ function Alertas({
|
|
|
260
268
|
|
|
261
269
|
export default function DashboardPage() {
|
|
262
270
|
const t = useTranslations('finance.DashboardPage');
|
|
271
|
+
const locale = useLocale();
|
|
263
272
|
const { data } = useFinanceData();
|
|
264
273
|
const {
|
|
265
274
|
kpis,
|
|
@@ -268,6 +277,7 @@ export default function DashboardPage() {
|
|
|
268
277
|
titulosReceber,
|
|
269
278
|
extratos,
|
|
270
279
|
pessoas,
|
|
280
|
+
periodoAberto,
|
|
271
281
|
} = data;
|
|
272
282
|
|
|
273
283
|
const getPessoaById = (id?: string) => pessoas.find((p) => p.id === id);
|
|
@@ -324,7 +334,13 @@ export default function DashboardPage() {
|
|
|
324
334
|
<DashboardChart fluxoCaixaPrevisto={fluxoCaixaPrevisto} t={t} />
|
|
325
335
|
</CardContent>
|
|
326
336
|
</Card>
|
|
327
|
-
<Alertas
|
|
337
|
+
<Alertas
|
|
338
|
+
titulosPagar={titulosPagar}
|
|
339
|
+
extratos={extratos}
|
|
340
|
+
periodoAberto={periodoAberto}
|
|
341
|
+
locale={locale}
|
|
342
|
+
t={t}
|
|
343
|
+
/>
|
|
328
344
|
</div>
|
|
329
345
|
|
|
330
346
|
<ProximosVencimentos
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hed-hog/finance",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.250",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"dependencies": {
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
"@nestjs/core": "^11",
|
|
10
10
|
"@nestjs/jwt": "^11",
|
|
11
11
|
"@nestjs/mapped-types": "*",
|
|
12
|
+
"@hed-hog/tag": "0.0.250",
|
|
12
13
|
"@hed-hog/api-pagination": "0.0.5",
|
|
13
|
-
"@hed-hog/
|
|
14
|
+
"@hed-hog/contact": "0.0.250",
|
|
14
15
|
"@hed-hog/api-prisma": "0.0.4",
|
|
15
|
-
"@hed-hog/api": "0.0.3",
|
|
16
|
-
"@hed-hog/contact": "0.0.240",
|
|
17
16
|
"@hed-hog/api-types": "0.0.1",
|
|
18
17
|
"@hed-hog/api-locale": "0.0.11",
|
|
19
|
-
"@hed-hog/
|
|
18
|
+
"@hed-hog/api": "0.0.3",
|
|
19
|
+
"@hed-hog/core": "0.0.250"
|
|
20
20
|
},
|
|
21
21
|
"exports": {
|
|
22
22
|
".": {
|
package/src/finance.service.ts
CHANGED
|
@@ -728,6 +728,7 @@ export class FinanceService {
|
|
|
728
728
|
bankAccountsResult,
|
|
729
729
|
tagsResult,
|
|
730
730
|
auditLogsResult,
|
|
731
|
+
openPeriodResult,
|
|
731
732
|
] = await Promise.allSettled([
|
|
732
733
|
this.loadTitles('payable'),
|
|
733
734
|
this.loadTitles('receivable'),
|
|
@@ -737,6 +738,7 @@ export class FinanceService {
|
|
|
737
738
|
this.loadBankAccounts(),
|
|
738
739
|
this.loadTags(),
|
|
739
740
|
this.loadAuditLogs(),
|
|
741
|
+
this.loadOpenPeriod(),
|
|
740
742
|
]);
|
|
741
743
|
|
|
742
744
|
const payables = payablesResult.status === 'fulfilled' ? payablesResult.value : [];
|
|
@@ -752,6 +754,8 @@ export class FinanceService {
|
|
|
752
754
|
const tags = tagsResult.status === 'fulfilled' ? tagsResult.value : [];
|
|
753
755
|
const auditLogs =
|
|
754
756
|
auditLogsResult.status === 'fulfilled' ? auditLogsResult.value : [];
|
|
757
|
+
const openPeriod =
|
|
758
|
+
openPeriodResult.status === 'fulfilled' ? openPeriodResult.value : null;
|
|
755
759
|
|
|
756
760
|
if (payablesResult.status === 'rejected') {
|
|
757
761
|
this.logger.error('Failed to load finance payables', payablesResult.reason);
|
|
@@ -777,6 +781,9 @@ export class FinanceService {
|
|
|
777
781
|
if (auditLogsResult.status === 'rejected') {
|
|
778
782
|
this.logger.error('Failed to load finance audit logs', auditLogsResult.reason);
|
|
779
783
|
}
|
|
784
|
+
if (openPeriodResult.status === 'rejected') {
|
|
785
|
+
this.logger.error('Failed to load finance open period', openPeriodResult.reason);
|
|
786
|
+
}
|
|
780
787
|
|
|
781
788
|
const aprovacoesPendentes = payables
|
|
782
789
|
.filter((title: any) => title.status === 'rascunho')
|
|
@@ -790,15 +797,10 @@ export class FinanceService {
|
|
|
790
797
|
dataSolicitacao: title.criadoEm,
|
|
791
798
|
}));
|
|
792
799
|
|
|
800
|
+
const kpis = this.calculateDashboardKpis(payables, receivables, bankAccounts);
|
|
801
|
+
|
|
793
802
|
return {
|
|
794
|
-
kpis
|
|
795
|
-
saldoCaixa: 0,
|
|
796
|
-
aPagar30dias: 0,
|
|
797
|
-
aPagar7dias: 0,
|
|
798
|
-
aReceber30dias: 0,
|
|
799
|
-
aReceber7dias: 0,
|
|
800
|
-
inadimplencia: 0,
|
|
801
|
-
},
|
|
803
|
+
kpis,
|
|
802
804
|
fluxoCaixaPrevisto: [],
|
|
803
805
|
titulosPagar: payables,
|
|
804
806
|
titulosReceber: receivables,
|
|
@@ -818,9 +820,96 @@ export class FinanceService {
|
|
|
818
820
|
historicoContatos: [],
|
|
819
821
|
entradasPrevistas: [],
|
|
820
822
|
saidasPrevistas: [],
|
|
823
|
+
periodoAberto: openPeriod,
|
|
821
824
|
};
|
|
822
825
|
}
|
|
823
826
|
|
|
827
|
+
private calculateDashboardKpis(
|
|
828
|
+
payables: any[],
|
|
829
|
+
receivables: any[],
|
|
830
|
+
bankAccounts: any[],
|
|
831
|
+
) {
|
|
832
|
+
const today = this.startOfDay(new Date());
|
|
833
|
+
const day7 = this.addDays(today, 7);
|
|
834
|
+
const day30 = this.addDays(today, 30);
|
|
835
|
+
|
|
836
|
+
const payableInstallments = this.extractOpenInstallments(payables);
|
|
837
|
+
const receivableInstallments = this.extractOpenInstallments(receivables);
|
|
838
|
+
|
|
839
|
+
const saldoCaixa = (bankAccounts || [])
|
|
840
|
+
.filter((account) => account?.ativo !== false)
|
|
841
|
+
.reduce((acc, account) => acc + Number(account?.saldoAtual || 0), 0);
|
|
842
|
+
|
|
843
|
+
const aPagar7dias = this.sumInstallmentsDueBetween(
|
|
844
|
+
payableInstallments,
|
|
845
|
+
today,
|
|
846
|
+
day7,
|
|
847
|
+
);
|
|
848
|
+
const aPagar30dias = this.sumInstallmentsDueBetween(
|
|
849
|
+
payableInstallments,
|
|
850
|
+
today,
|
|
851
|
+
day30,
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
const aReceber7dias = this.sumInstallmentsDueBetween(
|
|
855
|
+
receivableInstallments,
|
|
856
|
+
today,
|
|
857
|
+
day7,
|
|
858
|
+
);
|
|
859
|
+
const aReceber30dias = this.sumInstallmentsDueBetween(
|
|
860
|
+
receivableInstallments,
|
|
861
|
+
today,
|
|
862
|
+
day30,
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
const inadimplencia = receivableInstallments
|
|
866
|
+
.filter((installment) => this.startOfDay(new Date(installment.vencimento)) < today)
|
|
867
|
+
.reduce((acc, installment) => acc + Number(installment.valor || 0), 0);
|
|
868
|
+
|
|
869
|
+
return {
|
|
870
|
+
saldoCaixa: Number(saldoCaixa.toFixed(2)),
|
|
871
|
+
aPagar30dias: Number(aPagar30dias.toFixed(2)),
|
|
872
|
+
aPagar7dias: Number(aPagar7dias.toFixed(2)),
|
|
873
|
+
aReceber30dias: Number(aReceber30dias.toFixed(2)),
|
|
874
|
+
aReceber7dias: Number(aReceber7dias.toFixed(2)),
|
|
875
|
+
inadimplencia: Number(inadimplencia.toFixed(2)),
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
private extractOpenInstallments(titles: any[]) {
|
|
880
|
+
return (titles || []).flatMap((title) =>
|
|
881
|
+
(title?.parcelas || []).filter(
|
|
882
|
+
(installment) =>
|
|
883
|
+
installment?.status === 'aberto' ||
|
|
884
|
+
installment?.status === 'vencido' ||
|
|
885
|
+
installment?.status === 'parcial',
|
|
886
|
+
),
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
private sumInstallmentsDueBetween(
|
|
891
|
+
installments: any[],
|
|
892
|
+
startDate: Date,
|
|
893
|
+
endDate: Date,
|
|
894
|
+
) {
|
|
895
|
+
return (installments || [])
|
|
896
|
+
.filter((installment) => {
|
|
897
|
+
const dueDate = this.startOfDay(new Date(installment.vencimento));
|
|
898
|
+
return dueDate >= startDate && dueDate <= endDate;
|
|
899
|
+
})
|
|
900
|
+
.reduce((acc, installment) => acc + Number(installment.valor || 0), 0);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
private startOfDay(date: Date) {
|
|
904
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
private addDays(date: Date, days: number) {
|
|
908
|
+
const next = new Date(date);
|
|
909
|
+
next.setDate(next.getDate() + days);
|
|
910
|
+
return next;
|
|
911
|
+
}
|
|
912
|
+
|
|
824
913
|
async listAccountsPayableInstallments(
|
|
825
914
|
paginationParams: PaginationDTO,
|
|
826
915
|
status?: string,
|
|
@@ -3416,6 +3505,30 @@ export class FinanceService {
|
|
|
3416
3505
|
}));
|
|
3417
3506
|
}
|
|
3418
3507
|
|
|
3508
|
+
private async loadOpenPeriod() {
|
|
3509
|
+
const openPeriod = await this.prisma.period_close.findFirst({
|
|
3510
|
+
where: {
|
|
3511
|
+
status: 'open',
|
|
3512
|
+
},
|
|
3513
|
+
orderBy: {
|
|
3514
|
+
period_start: 'desc',
|
|
3515
|
+
},
|
|
3516
|
+
select: {
|
|
3517
|
+
period_start: true,
|
|
3518
|
+
period_end: true,
|
|
3519
|
+
},
|
|
3520
|
+
});
|
|
3521
|
+
|
|
3522
|
+
if (!openPeriod) {
|
|
3523
|
+
return null;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
return {
|
|
3527
|
+
inicio: openPeriod.period_start?.toISOString?.() || null,
|
|
3528
|
+
fim: openPeriod.period_end?.toISOString?.() || null,
|
|
3529
|
+
};
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3419
3532
|
private async resolvePaymentMethodId(tx: any, paymentChannel?: string) {
|
|
3420
3533
|
const paymentType = this.mapPaymentMethodFromPt(paymentChannel);
|
|
3421
3534
|
|