Run SQL to create credit card tables.
This commit is contained in:
parent
cb3f41b8ff
commit
47e003213d
@ -1,3 +1,4 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@ -22,7 +23,6 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
// Opções para as listas suspensas
|
||||
const bandeirasCartao = [
|
||||
'Visa', 'Mastercard', 'Elo', 'American Express', 'Hipercard',
|
||||
'Diners Club', 'Credicard', 'Sorocred', 'Cabal', 'Banescard',
|
||||
@ -39,6 +39,9 @@ const cartaoSchema = z.object({
|
||||
nome: z.string().min(1, { message: 'Nome do cartão é obrigatório' }),
|
||||
bandeira: z.string({ required_error: 'Selecione uma bandeira' }),
|
||||
banco: z.string({ required_error: 'Selecione um banco' }),
|
||||
limite_total: z.string().optional(),
|
||||
dia_vencimento: z.string().optional(),
|
||||
melhor_dia_compra: z.string().optional(),
|
||||
});
|
||||
|
||||
type CartaoFormValues = z.infer<typeof cartaoSchema>;
|
||||
@ -58,6 +61,9 @@ export function CartaoCreditoForm({ onSuccess, onCancel }: CartaoCreditoFormProp
|
||||
nome: '',
|
||||
bandeira: '',
|
||||
banco: '',
|
||||
limite_total: '',
|
||||
dia_vencimento: '10',
|
||||
melhor_dia_compra: '5',
|
||||
}
|
||||
});
|
||||
|
||||
@ -65,7 +71,6 @@ export function CartaoCreditoForm({ onSuccess, onCancel }: CartaoCreditoFormProp
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
// Removed the fourth argument that was causing the error
|
||||
const resultado = await criarCartao(
|
||||
data.nome,
|
||||
data.bandeira,
|
||||
@ -73,6 +78,23 @@ export function CartaoCreditoForm({ onSuccess, onCancel }: CartaoCreditoFormProp
|
||||
);
|
||||
|
||||
if (resultado) {
|
||||
// Atualizar campos adicionais se fornecidos
|
||||
if (data.limite_total || data.dia_vencimento || data.melhor_dia_compra) {
|
||||
const { supabase } = await import('@/integrations/supabase/client');
|
||||
const userEmail = localStorage.getItem('userEmail');
|
||||
|
||||
const updateData: any = {};
|
||||
if (data.limite_total) updateData.limite_total = parseFloat(data.limite_total);
|
||||
if (data.dia_vencimento) updateData.dia_vencimento = parseInt(data.dia_vencimento);
|
||||
if (data.melhor_dia_compra) updateData.melhor_dia_compra = parseInt(data.melhor_dia_compra);
|
||||
|
||||
await supabase
|
||||
.from('cartoes_credito')
|
||||
.update(updateData)
|
||||
.eq('id', resultado.id)
|
||||
.eq('login', userEmail?.trim().toLowerCase());
|
||||
}
|
||||
|
||||
toast({
|
||||
title: "Cartão adicionado",
|
||||
description: "Cartão de crédito cadastrado com sucesso",
|
||||
@ -163,6 +185,67 @@ export function CartaoCreditoForm({ onSuccess, onCancel }: CartaoCreditoFormProp
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="limite_total"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Limite Total (Opcional)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
step="0.01"
|
||||
placeholder="0,00"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dia_vencimento"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Dia do Vencimento</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="31"
|
||||
placeholder="10"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="melhor_dia_compra"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Melhor Dia de Compra</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
min="1"
|
||||
max="31"
|
||||
placeholder="5"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2 pt-2">
|
||||
<Button variant="outline" type="button" onClick={onCancel} disabled={isSubmitting}>
|
||||
|
||||
281
src/components/credito/CartaoDetalhesAvancado.tsx
Normal file
281
src/components/credito/CartaoDetalhesAvancado.tsx
Normal file
@ -0,0 +1,281 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { CartaoCredito, DespesaCartao, FaturaCartao } from "@/types/cartaoTypes";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { CreditCard, Calendar, DollarSign, TrendingUp, FileText } from "lucide-react";
|
||||
import { FiltroFaturas } from "./FiltroFaturas";
|
||||
import { DespesasComParcelas } from "./DespesasComParcelas";
|
||||
import { useFaturas } from "@/hooks/useFaturas";
|
||||
import { formatCurrency } from "@/utils/formatters";
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
interface CartaoDetalhesAvancadoProps {
|
||||
cartao: CartaoCredito;
|
||||
despesas: DespesaCartao[];
|
||||
isLoading: boolean;
|
||||
onDespesaSuccess: () => void;
|
||||
}
|
||||
|
||||
export function CartaoDetalhesAvancado({
|
||||
cartao,
|
||||
despesas: todasDespesas,
|
||||
isLoading,
|
||||
onDespesaSuccess
|
||||
}: CartaoDetalhesAvancadoProps) {
|
||||
const [faturaAtual, setFaturaAtual] = useState<{ mes: number; ano: number } | null>(null);
|
||||
const [despesasFiltradas, setDespesasFiltradas] = useState<DespesaCartao[]>([]);
|
||||
const [faturaSelecionada, setFaturaSelecionada] = useState<FaturaCartao | null>(null);
|
||||
const { toast } = useToast();
|
||||
|
||||
const {
|
||||
faturas,
|
||||
isLoading: loadingFaturas,
|
||||
calcularFaturaAtual,
|
||||
criarFatura,
|
||||
atualizarStatusPagamento
|
||||
} = useFaturas({ cartaoId: cartao.id });
|
||||
|
||||
useEffect(() => {
|
||||
// Calcular fatura atual baseada no dia de vencimento
|
||||
const fatura = calcularFaturaAtual(cartao.dia_vencimento, cartao.melhor_dia_compra);
|
||||
setFaturaAtual(fatura);
|
||||
}, [cartao, calcularFaturaAtual]);
|
||||
|
||||
useEffect(() => {
|
||||
// Filtrar despesas pela fatura selecionada
|
||||
if (faturaAtual) {
|
||||
const despesasDaFatura = todasDespesas.filter(despesa =>
|
||||
despesa.mes_fatura === faturaAtual.mes && despesa.ano_fatura === faturaAtual.ano
|
||||
);
|
||||
setDespesasFiltradas(despesasDaFatura);
|
||||
|
||||
// Buscar dados da fatura
|
||||
const fatura = faturas.find(f => f.mes === faturaAtual.mes && f.ano === faturaAtual.ano);
|
||||
setFaturaSelecionada(fatura || null);
|
||||
}
|
||||
}, [faturaAtual, todasDespesas, faturas]);
|
||||
|
||||
const handleFaturaChange = (mes: number, ano: number) => {
|
||||
setFaturaAtual({ mes, ano });
|
||||
};
|
||||
|
||||
const handleCriarFatura = async (mes: number, ano: number) => {
|
||||
const novaFatura = await criarFatura(mes, ano, cartao.dia_vencimento);
|
||||
if (novaFatura) {
|
||||
toast({
|
||||
title: "Fatura criada",
|
||||
description: `Fatura de ${mes}/${ano} criada com sucesso`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditDespesa = (despesa: DespesaCartao) => {
|
||||
// TODO: Implementar edição de despesa
|
||||
console.log('Editar despesa:', despesa);
|
||||
};
|
||||
|
||||
const handleDeleteDespesa = async (id: string) => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('despesas_cartao')
|
||||
.delete()
|
||||
.eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
onDespesaSuccess();
|
||||
toast({
|
||||
title: "Despesa removida",
|
||||
description: "Despesa removida com sucesso",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao remover despesa:', error);
|
||||
toast({
|
||||
title: "Erro ao remover despesa",
|
||||
description: "Não foi possível remover a despesa",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleConciliacao = async (id: string, novoStatus: 'pendente' | 'conciliado' | 'divergente') => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('despesas_cartao')
|
||||
.update({ status_conciliacao: novoStatus })
|
||||
.eq('id', id);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
onDespesaSuccess();
|
||||
toast({
|
||||
title: "Status atualizado",
|
||||
description: `Despesa marcada como ${novoStatus}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar status:', error);
|
||||
toast({
|
||||
title: "Erro ao atualizar status",
|
||||
description: "Não foi possível atualizar o status da despesa",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const valorTotalFatura = despesasFiltradas.reduce((total, despesa) => total + despesa.valor, 0);
|
||||
const totalConciliado = despesasFiltradas.filter(d => d.status_conciliacao === 'conciliado').length;
|
||||
const totalPendente = despesasFiltradas.filter(d => d.status_conciliacao === 'pendente').length;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header do Cartão */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<CreditCard className="h-8 w-8 text-blue-600" />
|
||||
<div>
|
||||
<CardTitle className="text-2xl">{cartao.nome}</CardTitle>
|
||||
<p className="text-muted-foreground">
|
||||
{cartao.banco} • {cartao.bandeira}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<p className="text-sm text-muted-foreground">Limite Total</p>
|
||||
<p className="text-xl font-semibold">
|
||||
{cartao.limite_total ? formatCurrency(cartao.limite_total) : 'N/A'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{/* Filtro de Faturas */}
|
||||
{faturaAtual && (
|
||||
<FiltroFaturas
|
||||
faturas={faturas}
|
||||
faturaAtual={faturaAtual}
|
||||
onFaturaChange={handleFaturaChange}
|
||||
onCriarFatura={handleCriarFatura}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Resumo da Fatura */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<DollarSign className="h-5 w-5 text-green-600" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Valor Total</p>
|
||||
<p className="text-lg font-semibold">{formatCurrency(valorTotalFatura)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<FileText className="h-5 w-5 text-blue-600" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Total Despesas</p>
|
||||
<p className="text-lg font-semibold">{despesasFiltradas.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<TrendingUp className="h-5 w-5 text-green-600" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Conciliadas</p>
|
||||
<p className="text-lg font-semibold">{totalConciliado}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar className="h-5 w-5 text-yellow-600" />
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Pendentes</p>
|
||||
<p className="text-lg font-semibold">{totalPendente}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Status da Fatura */}
|
||||
{faturaSelecionada && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>Status da Fatura</CardTitle>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge variant={
|
||||
faturaSelecionada.status_pagamento === 'pago' ? 'default' :
|
||||
faturaSelecionada.status_pagamento === 'vencido' ? 'destructive' : 'secondary'
|
||||
}>
|
||||
{faturaSelecionada.status_pagamento}
|
||||
</Badge>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => atualizarStatusPagamento(
|
||||
faturaSelecionada.id,
|
||||
faturaSelecionada.status_pagamento === 'pago' ? 'pendente' : 'pago',
|
||||
faturaSelecionada.status_pagamento === 'pago' ? undefined : new Date().toISOString().split('T')[0]
|
||||
)}
|
||||
>
|
||||
{faturaSelecionada.status_pagamento === 'pago' ? 'Marcar como Pendente' : 'Marcar como Pago'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-muted-foreground">Data de Vencimento</p>
|
||||
<p>{faturaSelecionada.data_vencimento ? new Date(faturaSelecionada.data_vencimento).toLocaleDateString('pt-BR') : 'N/A'}</p>
|
||||
</div>
|
||||
{faturaSelecionada.data_pagamento && (
|
||||
<div>
|
||||
<p className="text-muted-foreground">Data de Pagamento</p>
|
||||
<p>{new Date(faturaSelecionada.data_pagamento).toLocaleDateString('pt-BR')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Despesas */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Despesas da Fatura</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<DespesasComParcelas
|
||||
despesas={despesasFiltradas}
|
||||
onEditDespesa={handleEditDespesa}
|
||||
onDeleteDespesa={handleDeleteDespesa}
|
||||
onToggleConciliacao={handleToggleConciliacao}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
188
src/components/credito/DespesasComParcelas.tsx
Normal file
188
src/components/credito/DespesasComParcelas.tsx
Normal file
@ -0,0 +1,188 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { DespesaCartao } from '@/types/cartaoTypes';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Edit, Trash2, Check, X, CreditCard } from 'lucide-react';
|
||||
import { formatCurrency } from '@/utils/formatters';
|
||||
|
||||
interface DespesasComParcelasProps {
|
||||
despesas: DespesaCartao[];
|
||||
onEditDespesa: (despesa: DespesaCartao) => void;
|
||||
onDeleteDespesa: (id: string) => void;
|
||||
onToggleConciliacao: (id: string, status: 'pendente' | 'conciliado' | 'divergente') => void;
|
||||
}
|
||||
|
||||
export function DespesasComParcelas({
|
||||
despesas,
|
||||
onEditDespesa,
|
||||
onDeleteDespesa,
|
||||
onToggleConciliacao
|
||||
}: DespesasComParcelasProps) {
|
||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
|
||||
|
||||
const toggleExpanded = (id: string) => {
|
||||
const newExpanded = new Set(expandedItems);
|
||||
if (newExpanded.has(id)) {
|
||||
newExpanded.delete(id);
|
||||
} else {
|
||||
newExpanded.add(id);
|
||||
}
|
||||
setExpandedItems(newExpanded);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const variants = {
|
||||
pendente: 'bg-yellow-100 text-yellow-800',
|
||||
conciliado: 'bg-green-100 text-green-800',
|
||||
divergente: 'bg-red-100 text-red-800'
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge className={variants[status as keyof typeof variants] || variants.pendente}>
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
// Agrupar despesas por despesa pai (para parcelas)
|
||||
const despesasAgrupadas = despesas.reduce((acc, despesa) => {
|
||||
const chave = despesa.despesa_pai_id || despesa.id;
|
||||
if (!acc[chave]) {
|
||||
acc[chave] = [];
|
||||
}
|
||||
acc[chave].push(despesa);
|
||||
return acc;
|
||||
}, {} as Record<string, DespesaCartao[]>);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{Object.entries(despesasAgrupadas).map(([chaveGrupo, grupoDesp]) => {
|
||||
const despesaPrincipal = grupoDesp.find(d => !d.despesa_pai_id) || grupoDesp[0];
|
||||
const parcelas = grupoDesp.filter(d => d.despesa_pai_id);
|
||||
const temParcelas = parcelas.length > 0 || (despesaPrincipal.total_parcelas && despesaPrincipal.total_parcelas > 1);
|
||||
const isExpanded = expandedItems.has(chaveGrupo);
|
||||
|
||||
return (
|
||||
<Card key={chaveGrupo} className="w-full">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<CreditCard className="h-5 w-5 text-blue-600" />
|
||||
<div>
|
||||
<CardTitle className="text-lg">{despesaPrincipal.descricao}</CardTitle>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{new Date(despesaPrincipal.data_despesa).toLocaleDateString('pt-BR')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{getStatusBadge(despesaPrincipal.status_conciliacao || 'pendente')}
|
||||
|
||||
<div className="text-right">
|
||||
<p className="text-lg font-semibold">
|
||||
{formatCurrency(despesaPrincipal.valor)}
|
||||
</p>
|
||||
{temParcelas && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{despesaPrincipal.parcela_atual}/{despesaPrincipal.total_parcelas} parcelas
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-3">
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onEditDespesa(despesaPrincipal)}
|
||||
>
|
||||
<Edit className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onDeleteDespesa(despesaPrincipal.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onToggleConciliacao(
|
||||
despesaPrincipal.id,
|
||||
despesaPrincipal.status_conciliacao === 'conciliado' ? 'pendente' : 'conciliado'
|
||||
)}
|
||||
>
|
||||
{despesaPrincipal.status_conciliacao === 'conciliado' ? (
|
||||
<X className="h-4 w-4" />
|
||||
) : (
|
||||
<Check className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{temParcelas && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => toggleExpanded(chaveGrupo)}
|
||||
>
|
||||
{isExpanded ? 'Ocultar' : 'Ver'} Parcelas
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
{isExpanded && temParcelas && (
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Parcela</TableHead>
|
||||
<TableHead>Valor</TableHead>
|
||||
<TableHead>Fatura</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{[despesaPrincipal, ...parcelas].map((parcela, index) => (
|
||||
<TableRow key={parcela.id}>
|
||||
<TableCell>
|
||||
{parcela.parcela_atual || index + 1}/{parcela.total_parcelas}
|
||||
</TableCell>
|
||||
<TableCell>{formatCurrency(parcela.valor)}</TableCell>
|
||||
<TableCell>
|
||||
{parcela.mes_fatura && parcela.ano_fatura
|
||||
? `${parcela.mes_fatura.toString().padStart(2, '0')}/${parcela.ano_fatura}`
|
||||
: 'N/A'
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{getStatusBadge(parcela.status_conciliacao || 'pendente')}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
|
||||
{despesas.length === 0 && (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
Nenhuma despesa encontrada para esta fatura.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
126
src/components/credito/FiltroFaturas.tsx
Normal file
126
src/components/credito/FiltroFaturas.tsx
Normal file
@ -0,0 +1,126 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ChevronLeft, ChevronRight, Plus } from 'lucide-react';
|
||||
import { FaturaCartao } from '@/types/cartaoTypes';
|
||||
|
||||
interface FiltroFaturasProps {
|
||||
faturas: FaturaCartao[];
|
||||
faturaAtual: { mes: number; ano: number } | null;
|
||||
onFaturaChange: (mes: number, ano: number) => void;
|
||||
onCriarFatura: (mes: number, ano: number) => void;
|
||||
}
|
||||
|
||||
const meses = [
|
||||
'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
|
||||
'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'
|
||||
];
|
||||
|
||||
export function FiltroFaturas({ faturas, faturaAtual, onFaturaChange, onCriarFatura }: FiltroFaturasProps) {
|
||||
const [mesSelecionado, setMesSelecionado] = useState<number>(faturaAtual?.mes || new Date().getMonth() + 1);
|
||||
const [anoSelecionado, setAnoSelecionado] = useState<number>(faturaAtual?.ano || new Date().getFullYear());
|
||||
|
||||
const faturaExiste = faturas.some(f => f.mes === mesSelecionado && f.ano === anoSelecionado);
|
||||
|
||||
const navegarMes = (direcao: 'anterior' | 'proximo') => {
|
||||
if (direcao === 'anterior') {
|
||||
if (mesSelecionado === 1) {
|
||||
setMesSelecionado(12);
|
||||
setAnoSelecionado(anoSelecionado - 1);
|
||||
} else {
|
||||
setMesSelecionado(mesSelecionado - 1);
|
||||
}
|
||||
} else {
|
||||
if (mesSelecionado === 12) {
|
||||
setMesSelecionado(1);
|
||||
setAnoSelecionado(anoSelecionado + 1);
|
||||
} else {
|
||||
setMesSelecionado(mesSelecionado + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onFaturaChange(mesSelecionado, anoSelecionado);
|
||||
}, [mesSelecionado, anoSelecionado, onFaturaChange]);
|
||||
|
||||
const gerarAnos = () => {
|
||||
const anoAtual = new Date().getFullYear();
|
||||
const anos = [];
|
||||
for (let ano = anoAtual - 2; ano <= anoAtual + 2; ano++) {
|
||||
anos.push(ano);
|
||||
}
|
||||
return anos;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between bg-card p-4 rounded-lg border">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => navegarMes('anterior')}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Select value={mesSelecionado.toString()} onValueChange={(value) => setMesSelecionado(parseInt(value))}>
|
||||
<SelectTrigger className="w-32">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{meses.map((mes, index) => (
|
||||
<SelectItem key={index + 1} value={(index + 1).toString()}>
|
||||
{mes}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select value={anoSelecionado.toString()} onValueChange={(value) => setAnoSelecionado(parseInt(value))}>
|
||||
<SelectTrigger className="w-20">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{gerarAnos().map((ano) => (
|
||||
<SelectItem key={ano} value={ano.toString()}>
|
||||
{ano}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => navegarMes('proximo')}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
{!faturaExiste && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onCriarFatura(mesSelecionado, anoSelecionado)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Criar Fatura
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className={`px-2 py-1 rounded text-sm ${
|
||||
faturaExiste ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'
|
||||
}`}>
|
||||
{faturaExiste ? 'Fatura Existente' : 'Fatura Não Criada'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
118
src/hooks/useFaturas.ts
Normal file
118
src/hooks/useFaturas.ts
Normal file
@ -0,0 +1,118 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { FaturaCartao } from '@/types/cartaoTypes';
|
||||
|
||||
interface UseFaturasProps {
|
||||
cartaoId: string;
|
||||
}
|
||||
|
||||
export function useFaturas({ cartaoId }: UseFaturasProps) {
|
||||
const [faturas, setFaturas] = useState<FaturaCartao[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [faturaAtual, setFaturaAtual] = useState<{ mes: number; ano: number } | null>(null);
|
||||
|
||||
const calcularFaturaAtual = (diaVencimento: number = 10, melhorDiaCompra: number = 5) => {
|
||||
const hoje = new Date();
|
||||
const diaAtual = hoje.getDate();
|
||||
const mesAtual = hoje.getMonth() + 1;
|
||||
const anoAtual = hoje.getFullYear();
|
||||
|
||||
// Se estamos antes do dia de vencimento, a fatura atual é do mês atual
|
||||
// Se estamos depois do dia de vencimento, a fatura atual é do próximo mês
|
||||
if (diaAtual <= diaVencimento) {
|
||||
return { mes: mesAtual, ano: anoAtual };
|
||||
} else {
|
||||
const proximoMes = mesAtual === 12 ? 1 : mesAtual + 1;
|
||||
const proximoAno = mesAtual === 12 ? anoAtual + 1 : anoAtual;
|
||||
return { mes: proximoMes, ano: proximoAno };
|
||||
}
|
||||
};
|
||||
|
||||
const loadFaturas = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const userEmail = localStorage.getItem('userEmail');
|
||||
if (!userEmail) return;
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('faturas_cartao')
|
||||
.select('*')
|
||||
.eq('cartao_id', cartaoId)
|
||||
.eq('login', userEmail.trim().toLowerCase())
|
||||
.order('ano', { ascending: false })
|
||||
.order('mes', { ascending: false });
|
||||
|
||||
if (error) throw error;
|
||||
setFaturas(data || []);
|
||||
} catch (error) {
|
||||
console.error('Erro ao carregar faturas:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const criarFatura = async (mes: number, ano: number, diaVencimento: number = 10) => {
|
||||
try {
|
||||
const userEmail = localStorage.getItem('userEmail');
|
||||
if (!userEmail) return null;
|
||||
|
||||
const dataVencimento = new Date(ano, mes - 1, diaVencimento);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('faturas_cartao')
|
||||
.insert({
|
||||
cartao_id: cartaoId,
|
||||
mes,
|
||||
ano,
|
||||
data_vencimento: dataVencimento.toISOString().split('T')[0],
|
||||
login: userEmail.trim().toLowerCase(),
|
||||
valor_total: 0
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
await loadFaturas();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar fatura:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const atualizarStatusPagamento = async (faturaId: string, status: 'pendente' | 'pago' | 'vencido', dataPagamento?: string) => {
|
||||
try {
|
||||
const { error } = await supabase
|
||||
.from('faturas_cartao')
|
||||
.update({
|
||||
status_pagamento: status,
|
||||
data_pagamento: dataPagamento || null
|
||||
})
|
||||
.eq('id', faturaId);
|
||||
|
||||
if (error) throw error;
|
||||
await loadFaturas();
|
||||
} catch (error) {
|
||||
console.error('Erro ao atualizar status da fatura:', error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (cartaoId) {
|
||||
loadFaturas();
|
||||
}
|
||||
}, [cartaoId]);
|
||||
|
||||
return {
|
||||
faturas,
|
||||
isLoading,
|
||||
faturaAtual,
|
||||
calcularFaturaAtual,
|
||||
setFaturaAtual,
|
||||
loadFaturas,
|
||||
criarFatura,
|
||||
atualizarStatusPagamento
|
||||
};
|
||||
}
|
||||
@ -101,23 +101,38 @@ export type Database = {
|
||||
}
|
||||
cartoes_credito: {
|
||||
Row: {
|
||||
banco: string | null
|
||||
bandeira: string | null
|
||||
created_at: string
|
||||
dia_vencimento: number | null
|
||||
id: string
|
||||
limite_total: number | null
|
||||
login: string | null
|
||||
melhor_dia_compra: number | null
|
||||
nome: string
|
||||
user_id: string
|
||||
}
|
||||
Insert: {
|
||||
banco?: string | null
|
||||
bandeira?: string | null
|
||||
created_at?: string
|
||||
dia_vencimento?: number | null
|
||||
id?: string
|
||||
limite_total?: number | null
|
||||
login?: string | null
|
||||
melhor_dia_compra?: number | null
|
||||
nome: string
|
||||
user_id: string
|
||||
}
|
||||
Update: {
|
||||
banco?: string | null
|
||||
bandeira?: string | null
|
||||
created_at?: string
|
||||
dia_vencimento?: number | null
|
||||
id?: string
|
||||
limite_total?: number | null
|
||||
login?: string | null
|
||||
melhor_dia_compra?: number | null
|
||||
nome?: string
|
||||
user_id?: string
|
||||
}
|
||||
@ -179,34 +194,58 @@ export type Database = {
|
||||
}
|
||||
despesas_cartao: {
|
||||
Row: {
|
||||
ano_fatura: number | null
|
||||
cartao_id: string | null
|
||||
created_at: string
|
||||
data_despesa: string
|
||||
descricao: string
|
||||
despesa_pai_id: string | null
|
||||
id: string
|
||||
login: string | null
|
||||
mes_fatura: number | null
|
||||
nome: string | null
|
||||
observacoes: string | null
|
||||
parcela_atual: number | null
|
||||
status_conciliacao: string | null
|
||||
total_parcelas: number | null
|
||||
valor: number
|
||||
valor_original: number | null
|
||||
}
|
||||
Insert: {
|
||||
ano_fatura?: number | null
|
||||
cartao_id?: string | null
|
||||
created_at?: string
|
||||
data_despesa: string
|
||||
descricao: string
|
||||
despesa_pai_id?: string | null
|
||||
id?: string
|
||||
login?: string | null
|
||||
mes_fatura?: number | null
|
||||
nome?: string | null
|
||||
observacoes?: string | null
|
||||
parcela_atual?: number | null
|
||||
status_conciliacao?: string | null
|
||||
total_parcelas?: number | null
|
||||
valor: number
|
||||
valor_original?: number | null
|
||||
}
|
||||
Update: {
|
||||
ano_fatura?: number | null
|
||||
cartao_id?: string | null
|
||||
created_at?: string
|
||||
data_despesa?: string
|
||||
descricao?: string
|
||||
despesa_pai_id?: string | null
|
||||
id?: string
|
||||
login?: string | null
|
||||
mes_fatura?: number | null
|
||||
nome?: string | null
|
||||
observacoes?: string | null
|
||||
parcela_atual?: number | null
|
||||
status_conciliacao?: string | null
|
||||
total_parcelas?: number | null
|
||||
valor?: number
|
||||
valor_original?: number | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
@ -216,6 +255,60 @@ export type Database = {
|
||||
referencedRelation: "cartoes_credito"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "despesas_cartao_despesa_pai_id_fkey"
|
||||
columns: ["despesa_pai_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "despesas_cartao"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
faturas_cartao: {
|
||||
Row: {
|
||||
ano: number
|
||||
cartao_id: string | null
|
||||
created_at: string | null
|
||||
data_pagamento: string | null
|
||||
data_vencimento: string | null
|
||||
id: string
|
||||
login: string | null
|
||||
mes: number
|
||||
status_pagamento: string | null
|
||||
valor_total: number | null
|
||||
}
|
||||
Insert: {
|
||||
ano: number
|
||||
cartao_id?: string | null
|
||||
created_at?: string | null
|
||||
data_pagamento?: string | null
|
||||
data_vencimento?: string | null
|
||||
id?: string
|
||||
login?: string | null
|
||||
mes: number
|
||||
status_pagamento?: string | null
|
||||
valor_total?: number | null
|
||||
}
|
||||
Update: {
|
||||
ano?: number
|
||||
cartao_id?: string | null
|
||||
created_at?: string | null
|
||||
data_pagamento?: string | null
|
||||
data_vencimento?: string | null
|
||||
id?: string
|
||||
login?: string | null
|
||||
mes?: number
|
||||
status_pagamento?: string | null
|
||||
valor_total?: number | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "faturas_cartao_cartao_id_fkey"
|
||||
columns: ["cartao_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "cartoes_credito"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
grupos_whatsapp: {
|
||||
|
||||
@ -5,7 +5,7 @@ import { CartaoCredito, DespesaCartao } from '@/types/cartaoTypes';
|
||||
import { getCartoes, getDespesasCartao, getTotalDespesasCartao } from '@/services/cartaoCreditoService';
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { CartaoActions } from '@/components/credito/CartaoActions';
|
||||
import { CartaoDetalhes } from '@/components/credito/CartaoDetalhes';
|
||||
import { CartaoDetalhesAvancado } from '@/components/credito/CartaoDetalhesAvancado';
|
||||
import { CartaoListView } from '@/components/credito/CartaoListView';
|
||||
|
||||
const CartoesCreditoPage = () => {
|
||||
@ -21,7 +21,6 @@ const CartoesCreditoPage = () => {
|
||||
setIsLoading(true);
|
||||
const data = await getCartoes();
|
||||
|
||||
// Load the totals for each card
|
||||
const cartoesWithTotals = await Promise.all(
|
||||
data.map(async (cartao) => {
|
||||
const total_despesas = await getTotalDespesasCartao(cartao.id);
|
||||
@ -43,18 +42,12 @@ const CartoesCreditoPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadCartoes();
|
||||
}, []);
|
||||
|
||||
const loadDespesasCartao = async (cartaoId: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
// This function now uses login+nome matching internally instead of cartao_id
|
||||
const despesasData = await getDespesasCartao(cartaoId);
|
||||
setDespesas(despesasData);
|
||||
|
||||
// Also refresh the total for the selected card
|
||||
if (cartaoSelecionado) {
|
||||
const totalDespesas = await getTotalDespesasCartao(cartaoId);
|
||||
console.log(`Total atualizado para cartão ${cartaoSelecionado.nome}: ${totalDespesas}`);
|
||||
@ -84,10 +77,8 @@ const CartoesCreditoPage = () => {
|
||||
};
|
||||
|
||||
const handleDespesaSuccess = () => {
|
||||
// Recarregar detalhes do cartão após adicionar despesa
|
||||
if (cartaoSelecionado) {
|
||||
loadDespesasCartao(cartaoSelecionado.id);
|
||||
// Recarregar também a lista de cartões para atualizar os totais
|
||||
loadCartoes();
|
||||
}
|
||||
toast({
|
||||
@ -97,7 +88,6 @@ const CartoesCreditoPage = () => {
|
||||
};
|
||||
|
||||
const handleCartaoClick = async (cartao: CartaoCredito) => {
|
||||
// Refresh the total first
|
||||
const totalDespesas = await getTotalDespesasCartao(cartao.id);
|
||||
const updatedCartao = { ...cartao, total_despesas: totalDespesas };
|
||||
|
||||
@ -112,6 +102,10 @@ const CartoesCreditoPage = () => {
|
||||
setDespesas([]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadCartoes();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="space-y-6">
|
||||
@ -130,10 +124,11 @@ const CartoesCreditoPage = () => {
|
||||
onCartaoClick={handleCartaoClick}
|
||||
/>
|
||||
) : cartaoSelecionado && (
|
||||
<CartaoDetalhes
|
||||
<CartaoDetalhesAvancado
|
||||
cartao={cartaoSelecionado}
|
||||
despesas={despesas}
|
||||
isLoading={isLoading}
|
||||
onDespesaSuccess={handleDespesaSuccess}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -8,6 +8,9 @@ export interface CartaoCredito {
|
||||
user_id: string;
|
||||
login?: string;
|
||||
created_at: string;
|
||||
limite_total?: number;
|
||||
dia_vencimento?: number;
|
||||
melhor_dia_compra?: number;
|
||||
total_despesas?: number;
|
||||
}
|
||||
|
||||
@ -21,4 +24,25 @@ export interface DespesaCartao {
|
||||
data_despesa: string;
|
||||
descricao: string;
|
||||
created_at: string;
|
||||
parcela_atual?: number;
|
||||
total_parcelas?: number;
|
||||
valor_original?: number;
|
||||
despesa_pai_id?: string;
|
||||
status_conciliacao?: 'pendente' | 'conciliado' | 'divergente';
|
||||
mes_fatura?: number;
|
||||
ano_fatura?: number;
|
||||
observacoes?: string;
|
||||
}
|
||||
|
||||
export interface FaturaCartao {
|
||||
id: string;
|
||||
cartao_id: string;
|
||||
mes: number;
|
||||
ano: number;
|
||||
valor_total: number;
|
||||
data_vencimento?: string;
|
||||
status_pagamento: 'pendente' | 'pago' | 'vencido';
|
||||
data_pagamento?: string;
|
||||
login?: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
20
src/utils/formatters.ts
Normal file
20
src/utils/formatters.ts
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
export const formatCurrency = (value: number): string => {
|
||||
return new Intl.NumberFormat('pt-BR', {
|
||||
style: 'currency',
|
||||
currency: 'BRL',
|
||||
}).format(value);
|
||||
};
|
||||
|
||||
export const formatDate = (date: string | Date): string => {
|
||||
const dateObj = typeof date === 'string' ? new Date(date) : date;
|
||||
return dateObj.toLocaleDateString('pt-BR');
|
||||
};
|
||||
|
||||
export const formatPercent = (value: number): string => {
|
||||
return new Intl.NumberFormat('pt-BR', {
|
||||
style: 'percent',
|
||||
minimumFractionDigits: 1,
|
||||
maximumFractionDigits: 1,
|
||||
}).format(value / 100);
|
||||
};
|
||||
@ -0,0 +1,39 @@
|
||||
|
||||
-- Adicionar campos à tabela de cartões de crédito
|
||||
ALTER TABLE cartoes_credito
|
||||
ADD COLUMN IF NOT EXISTS limite_total DECIMAL(10,2),
|
||||
ADD COLUMN IF NOT EXISTS dia_vencimento INTEGER DEFAULT 10,
|
||||
ADD COLUMN IF NOT EXISTS melhor_dia_compra INTEGER DEFAULT 5,
|
||||
ADD COLUMN IF NOT EXISTS bandeira TEXT,
|
||||
ADD COLUMN IF NOT EXISTS banco TEXT;
|
||||
|
||||
-- Criar tabela para faturas dos cartões
|
||||
CREATE TABLE IF NOT EXISTS faturas_cartao (
|
||||
id UUID NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
cartao_id UUID REFERENCES cartoes_credito(id) ON DELETE CASCADE,
|
||||
mes INTEGER NOT NULL,
|
||||
ano INTEGER NOT NULL,
|
||||
valor_total DECIMAL(10,2) DEFAULT 0,
|
||||
data_vencimento DATE,
|
||||
status_pagamento TEXT DEFAULT 'pendente' CHECK (status_pagamento IN ('pendente', 'pago', 'vencido')),
|
||||
data_pagamento DATE,
|
||||
login TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT now(),
|
||||
UNIQUE(cartao_id, mes, ano)
|
||||
);
|
||||
|
||||
-- Atualizar tabela de despesas para incluir parcelas
|
||||
ALTER TABLE despesas_cartao
|
||||
ADD COLUMN IF NOT EXISTS parcela_atual INTEGER DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS total_parcelas INTEGER DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS valor_original DECIMAL(10,2),
|
||||
ADD COLUMN IF NOT EXISTS despesa_pai_id UUID REFERENCES despesas_cartao(id),
|
||||
ADD COLUMN IF NOT EXISTS status_conciliacao TEXT DEFAULT 'pendente' CHECK (status_conciliacao IN ('pendente', 'conciliado', 'divergente')),
|
||||
ADD COLUMN IF NOT EXISTS mes_fatura INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS ano_fatura INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS observacoes TEXT;
|
||||
|
||||
-- Criar índices para melhor performance
|
||||
CREATE INDEX IF NOT EXISTS idx_faturas_cartao_mes_ano ON faturas_cartao(cartao_id, mes, ano);
|
||||
CREATE INDEX IF NOT EXISTS idx_despesas_cartao_fatura ON despesas_cartao(cartao_id, mes_fatura, ano_fatura);
|
||||
CREATE INDEX IF NOT EXISTS idx_despesas_cartao_pai ON despesas_cartao(despesa_pai_id);
|
||||
Loading…
Reference in New Issue
Block a user