Files
tracksteel_app/src/hooks/useEstoqueMovimentacoes.tsx

198 lines
6.7 KiB
TypeScript

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
export interface MovimentacaoEstoque {
id: string;
material_id: string;
tipo_movimentacao: 'entrada' | 'saida' | 'transferencia' | 'ajuste' | 'empenho' | 'desempenho';
quantidade: number;
valor_unitario?: number;
valor_total?: number;
lote?: string;
fornecedor?: string;
nota_fiscal?: string;
of_vinculada?: string;
observacoes?: string;
data_movimentacao: string;
created_at: string;
created_by?: string;
user_name?: string;
estoque_materiais?: {
codigo: string;
descricao: string;
};
}
export const useMovimentacoesEstoque = () => {
return useQuery({
queryKey: ['movimentacoes-estoque'],
queryFn: async () => {
const { data, error } = await supabase
.from('movimentacoes_estoque')
.select(`
*,
estoque_materiais (
codigo,
descricao
)
`)
.order('created_at', { ascending: false });
if (error) throw error;
return data as MovimentacaoEstoque[];
},
});
};
export const useCriarMovimentacao = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (movimentacao: {
material_id: string;
tipo_movimentacao: 'entrada' | 'saida' | 'transferencia' | 'ajuste' | 'empenho' | 'desempenho';
quantidade: number;
lote?: string;
fornecedor?: string;
nota_fiscal?: string;
of_vinculada?: string;
observacoes?: string;
data_movimentacao?: string;
valor_unitario?: number;
user_name?: string;
}) => {
// Validações básicas
if (!movimentacao.material_id) {
throw new Error('Material é obrigatório');
}
if (movimentacao.quantidade <= 0) {
throw new Error('Quantidade deve ser maior que zero');
}
// Validações específicas para empenho e desempenho
if (movimentacao.tipo_movimentacao === 'empenho' && !movimentacao.of_vinculada) {
throw new Error('OF vinculada é obrigatória para movimentações de empenho');
}
if (movimentacao.tipo_movimentacao === 'desempenho' && !movimentacao.of_vinculada) {
throw new Error('OF vinculada é obrigatória para movimentações de desempenho');
}
// Para empenhos e saídas, verificar disponibilidade
if (movimentacao.tipo_movimentacao === 'empenho' || movimentacao.tipo_movimentacao === 'saida') {
const { data: materialAtual, error: materialError } = await supabase
.from('estoque_materiais')
.select('quantidade_disponivel, descricao, codigo')
.eq('id', movimentacao.material_id)
.single();
if (materialError) {
throw new Error('Material não encontrado');
}
if (!materialAtual) {
throw new Error('Material não encontrado');
}
if (materialAtual.quantidade_disponivel < movimentacao.quantidade) {
const errorMsg = `Quantidade insuficiente para ${materialAtual.codigo} - ${materialAtual.descricao}. Disponível: ${materialAtual.quantidade_disponivel}, Solicitado: ${movimentacao.quantidade}`;
throw new Error(errorMsg);
}
}
// Preparar dados para inserção
const { data: currentUser } = await supabase.auth.getUser();
const dadosInsercao = {
material_id: movimentacao.material_id,
tipo_movimentacao: movimentacao.tipo_movimentacao,
quantidade: movimentacao.quantidade,
lote: movimentacao.lote || null,
fornecedor: movimentacao.fornecedor || null,
nota_fiscal: movimentacao.nota_fiscal || null,
of_vinculada: movimentacao.of_vinculada || null,
observacoes: movimentacao.observacoes || null,
data_movimentacao: movimentacao.data_movimentacao || new Date().toISOString().split('T')[0],
valor_unitario: movimentacao.valor_unitario || null,
created_by: currentUser.user?.id
};
// Criar a movimentação - o trigger do banco cuidará dos empenhos automaticamente
const { data: movimentacaoData, error: movError } = await supabase
.from('movimentacoes_estoque')
.insert([dadosInsercao])
.select()
.single();
if (movError) {
console.error('Erro detalhado:', movError);
throw new Error(movError.message || 'Erro ao criar movimentação');
}
if (!movimentacaoData) {
throw new Error('Nenhum dado foi retornado após a criação');
}
return movimentacaoData;
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['estoque-materiais'] });
queryClient.invalidateQueries({ queryKey: ['movimentacoes-estoque'] });
queryClient.invalidateQueries({ queryKey: ['empenhos-material'] });
toast.success('Movimentação criada com sucesso!');
},
onError: (error: Error) => {
console.error('Erro capturado na mutação:', error);
toast.error(error.message || 'Erro ao criar movimentação');
}
});
};
export const useExcluirMovimentacao = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (movimentacaoId: string) => {
// Buscar detalhes da movimentação
const { data: movimentacao, error: fetchError } = await supabase
.from('movimentacoes_estoque')
.select('*, estoque_materiais(codigo, descricao)')
.eq('id', movimentacaoId)
.single();
if (fetchError) {
throw new Error('Movimentação não encontrada');
}
// Excluir a movimentação - o trigger cuidará da reversão
const { error: deleteError } = await supabase
.from('movimentacoes_estoque')
.delete()
.eq('id', movimentacaoId);
if (deleteError) {
throw deleteError;
}
return movimentacao;
},
onSuccess: (movimentacao) => {
queryClient.invalidateQueries({ queryKey: ['movimentacoes-estoque'] });
queryClient.invalidateQueries({ queryKey: ['estoque-materiais'] });
queryClient.invalidateQueries({ queryKey: ['empenhos-material'] });
const materialInfo = movimentacao.estoque_materiais;
const materialDesc = materialInfo ? `${materialInfo.codigo} - ${materialInfo.descricao}` : 'Material';
toast.success(`Movimentação de ${movimentacao.tipo_movimentacao} para ${materialDesc} excluída com sucesso!`);
},
onError: (error: any) => {
console.error('Erro ao excluir movimentação:', error);
const errorMessage = error?.message || 'Erro ao excluir movimentação';
toast.error(errorMessage);
}
});
};