Files
tracksteel_app/src/hooks/useRomaneios.tsx
T

433 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { validarRomaneioParaEntrega } from '@/utils/validacaoRomaneio';
export interface RomaneioExpedicao {
id: string;
numero_romaneio: string;
of_number: string;
data_romaneio: string;
data_criacao: string;
revisao: number;
motivo_revisao?: string;
data_prevista_entrega?: string;
prioridade: string;
status: string;
observacoes?: string;
peso_total_romaneio: number;
previsao_kg?: number;
maior_dimensao?: string;
comprimento_maximo_veiculo?: number;
tipo_transporte?: string;
frete_tipo?: string;
nome_motorista?: string;
created_at: string;
updated_at: string;
created_by?: string;
itens_pecas?: ItemRomaneioPeca[];
itens_insumos?: ItemRomaneioInsumo[];
}
export interface ItemRomaneioPeca {
id: string;
romaneio_id: string;
peca_id: string;
marca: string;
fase?: string;
descricao?: string;
comprimento?: number;
peso_unitario: number;
quantidade_expedida: number;
peso_total: number;
quantidade_faltante: number;
created_at: string;
}
export interface ItemRomaneioInsumo {
id: string;
romaneio_id: string;
tipo_insumo: string;
descricao: string;
unidade: string;
quantidade_expedida: number;
peso_unitario?: number;
peso_total?: number;
observacoes?: string;
created_at: string;
}
export interface PecaPintura {
id: string;
marca: string;
etapa_fase?: string;
descricao?: string;
comprimento?: number | null;
peso_unitario: number;
quantidade_produzida_pintura: number;
quantidade_ja_expedida: number;
quantidade_disponivel: number;
of_number: string;
}
export const useRomaneios = (ofNumberFilter?: string) => {
return useQuery({
queryKey: ['romaneios', ofNumberFilter],
queryFn: async () => {
let query = supabase
.from('romaneios_expedicao')
.select(`
*,
itens_pecas:itens_romaneio_pecas(*),
itens_insumos:itens_romaneio_insumos(*)
`)
.order('created_at', { ascending: false });
if (ofNumberFilter && ofNumberFilter !== 'all') {
query = query.eq('of_number', ofNumberFilter);
}
const { data, error } = await query;
if (error) throw error;
// Calcular peso total correto das peças para cada romaneio
const normalizedData = data?.map(romaneio => {
const pesoTotalPecas = romaneio.itens_pecas?.reduce((sum: number, item: any) => sum + (item.peso_total || 0), 0) || 0;
const pesoTotalInsumos = romaneio.itens_insumos?.reduce((sum: number, item: any) => sum + (item.peso_total || 0), 0) || 0;
return {
...romaneio,
peso_total_romaneio: pesoTotalPecas + pesoTotalInsumos,
prioridade: romaneio.prioridade === 'Normal' || romaneio.prioridade === 'Urgente'
? romaneio.prioridade
: 'Normal',
status: ['Em planejamento', 'Confirmado', 'Expedido', 'Entregue', 'Conferido em Obra'].includes(romaneio.status)
? romaneio.status
: 'Em planejamento',
frete_tipo: romaneio.frete_tipo === 'terceiros' || romaneio.frete_tipo === 'proprio'
? romaneio.frete_tipo
: undefined
};
});
return normalizedData as RomaneioExpedicao[];
},
});
};
export const usePecasPintura = (ofNumber?: string) => {
return useQuery({
queryKey: ['pecas-pintura', ofNumber],
queryFn: async () => {
if (!ofNumber) return [];
// Buscar peças que passaram pelo processo de pintura
const { data: apontamentosPintura, error: apontamentosError } = await supabase
.from('apontamentos_producao')
.select(`
peca_id,
componente_id,
tipo_apontamento,
quantidade_produzida,
pecas (
id,
marca,
etapa_fase,
descricao,
quantidade,
peso_unitario,
comprimento,
of_number
),
processos_fabricacao (
nome
)
`)
.eq('of_number', ofNumber)
.ilike('processos_fabricacao.nome', '%pintura%');
if (apontamentosError) throw apontamentosError;
// Buscar quantidades já expedidas
const { data: itensExpedidos, error: expedidosError } = await supabase
.from('itens_romaneio_pecas')
.select('peca_id, quantidade_expedida')
.in('peca_id', apontamentosPintura?.map(a => a.peca_id).filter(Boolean) || []);
if (expedidosError) throw expedidosError;
// Calcular disponibilidade (apenas peças principais, ignorando componentes)
const pecasPintura: PecaPintura[] = [];
const pecasMap = new Map();
apontamentosPintura?.forEach(apontamento => {
if (apontamento.tipo_apontamento === 'componente' || apontamento.componente_id) return;
const pecaId = apontamento.peca_id;
const peca = apontamento.pecas;
if (!peca) return;
if (pecasMap.has(pecaId)) {
pecasMap.get(pecaId).quantidade_produzida_pintura += apontamento.quantidade_produzida;
} else {
pecasMap.set(pecaId, {
id: peca.id,
marca: peca.marca,
etapa_fase: peca.etapa_fase,
descricao: peca.descricao,
quantidade_total: peca.quantidade,
peso_unitario: peca.peso_unitario,
comprimento: peca.comprimento,
quantidade_produzida_pintura: apontamento.quantidade_produzida,
of_number: peca.of_number
});
}
});
pecasMap.forEach((peca, pecaId) => {
const totalExpedido = itensExpedidos
?.filter(item => item.peca_id === pecaId)
.reduce((sum, item) => sum + item.quantidade_expedida, 0) || 0;
const qtdProdLimitada = peca.quantidade_total
? Math.min(Number(peca.quantidade_total), peca.quantidade_produzida_pintura)
: peca.quantidade_produzida_pintura;
pecasPintura.push({
...peca,
quantidade_ja_expedida: totalExpedido,
quantidade_disponivel: qtdProdLimitada - totalExpedido
});
});
return pecasPintura.filter(p => p.quantidade_disponivel > 0);
},
enabled: !!ofNumber,
});
};
const generateRomaneioNumber = (ofNumber: string, existingRomaneios: RomaneioExpedicao[]) => {
// Extrair apenas o número da OF (sem o B)
const ofNumOnly = ofNumber.replace('B', '');
// Contar romaneios existentes para esta OF
const existingCount = existingRomaneios.filter(r => r.of_number === ofNumber).length;
const nextSequence = existingCount + 1;
return `RO${ofNumOnly}-${nextSequence.toString().padStart(2, '0')}`;
};
export const useCriarRomaneio = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (romaneio: Omit<RomaneioExpedicao, 'id' | 'numero_romaneio' | 'created_at' | 'updated_at'>) => {
const { data: existingRomaneios } = await supabase
.from('romaneios_expedicao')
.select('*')
.eq('of_number', romaneio.of_number);
const numeroRomaneio = generateRomaneioNumber(romaneio.of_number, existingRomaneios || []);
const { data, error } = await supabase
.from('romaneios_expedicao')
.insert([{
numero_romaneio: numeroRomaneio,
of_number: romaneio.of_number,
data_romaneio: romaneio.data_romaneio,
data_criacao: romaneio.data_criacao,
revisao: romaneio.revisao || 1,
motivo_revisao: romaneio.motivo_revisao,
data_prevista_entrega: romaneio.data_prevista_entrega,
prioridade: romaneio.prioridade,
status: romaneio.status,
observacoes: romaneio.observacoes,
peso_total_romaneio: romaneio.peso_total_romaneio,
previsao_kg: romaneio.previsao_kg,
maior_dimensao: romaneio.maior_dimensao,
tipo_transporte: romaneio.tipo_transporte,
frete_tipo: romaneio.frete_tipo,
nome_motorista: romaneio.nome_motorista,
created_by: (await supabase.auth.getUser()).data.user?.id
}])
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Romaneio criado com sucesso!');
},
onError: (error) => {
console.error('Erro ao criar romaneio:', error);
toast.error('Erro ao criar romaneio');
},
});
};
export const useAtualizarRomaneio = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, ...romaneio }: Partial<RomaneioExpedicao> & { id: string }) => {
// Buscar status atual antes da atualização
const { data: romaneioAtual, error: errorAtual } = await supabase
.from('romaneios_expedicao')
.select('status, numero_romaneio, of_number')
.eq('id', id)
.single();
if (errorAtual) {
console.error('❌ Erro ao buscar romaneio atual:', errorAtual);
throw errorAtual;
}
console.log('📋 Status atual do romaneio:', romaneioAtual?.status);
console.log('📋 Novo status a ser definido:', romaneio.status);
// Se status for Entregue e não passou pelo modal de confirmação, validar amigavelmente
if (romaneio.status === 'Entregue' && romaneioAtual?.status !== 'Entregue') {
const validacao = await validarRomaneioParaEntrega(id, romaneio.of_number || romaneioAtual?.of_number);
if (!validacao.valido) {
console.warn('⚠️ Romaneio possui pendências fabris ao passar para Entregue:', validacao.pendencias?.length);
}
}
const { data, error } = await supabase
.from('romaneios_expedicao')
.update(romaneio)
.eq('id', id)
.select(`
*,
itens_pecas:itens_romaneio_pecas(*)
`)
.single();
if (error) throw error;
// VERIFICAÇÃO ESPECÍFICA: Disparar apontamento automático APENAS quando status muda PARA "Entregue"
const statusAnterior = romaneioAtual?.status;
const novoStatus = romaneio.status;
console.log('🔍 Verificando mudança de status:');
console.log('- Status anterior:', statusAnterior);
console.log('- Novo status:', novoStatus);
if (novoStatus === 'Entregue' && statusAnterior !== 'Entregue') {
console.log('🚀 CONDIÇÃO ATENDIDA: Status mudou PARA "Entregue" - iniciando apontamento automático');
console.log('📦 Dados do romaneio atualizado:', data);
// Buscar dados completos do romaneio incluindo itens
const { data: romaneioCompleto, error: errorCompleto } = await supabase
.from('romaneios_expedicao')
.select(`
*,
itens_pecas:itens_romaneio_pecas(*)
`)
.eq('id', id)
.single();
if (errorCompleto) {
console.error('❌ Erro ao buscar dados completos do romaneio:', errorCompleto);
return data;
}
console.log('📋 Dados completos do romaneio para apontamento:', romaneioCompleto);
console.log('🔢 Número de peças no romaneio:', romaneioCompleto?.itens_pecas?.length || 0);
// Verificar se há peças para apontar
if (!romaneioCompleto?.itens_pecas || romaneioCompleto.itens_pecas.length === 0) {
console.log('⚠️ Romaneio sem peças - não será processado apontamento automático');
return data;
}
// Disparar evento customizado para apontamento automático
const eventDetail = {
romaneioId: id,
status: novoStatus,
romaneioData: romaneioCompleto,
statusAnterior: statusAnterior
};
console.log('🎯 Disparando evento romaneio-entregue para apontamento automático:', eventDetail);
// Usar setTimeout para garantir que o evento seja disparado após a atualização
setTimeout(() => {
window.dispatchEvent(new CustomEvent('romaneio-entregue', {
detail: eventDetail
}));
}, 100);
} else if (novoStatus === 'Entregue' && statusAnterior === 'Entregue') {
console.log('ℹ️ Status já era "Entregue" - apontamento automático não será disparado');
} else {
console.log(`ℹ️ Status atualizado de "${statusAnterior}" para "${novoStatus}" - apontamento automático não será disparado (só para mudança PARA "Entregue")`);
}
return data;
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Romaneio atualizado com sucesso!');
},
onError: (error: any) => {
console.error('Erro ao atualizar romaneio:', error);
toast.error(error?.message || 'Erro ao atualizar romaneio');
},
});
};
export const useAdicionarItemPeca = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (item: Omit<ItemRomaneioPeca, 'id' | 'created_at'>) => {
const { data, error } = await supabase
.from('itens_romaneio_pecas')
.insert([item])
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Item adicionado com sucesso!');
},
onError: (error) => {
console.error('Erro ao adicionar item:', error);
toast.error('Erro ao adicionar item');
},
});
};
export const useAdicionarItemInsumo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (item: Omit<ItemRomaneioInsumo, 'id' | 'created_at'>) => {
const { data, error } = await supabase
.from('itens_romaneio_insumos')
.insert([item])
.select()
.single();
if (error) throw error;
return data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['romaneios'] });
toast.success('Insumo adicionado com sucesso!');
},
onError: (error) => {
console.error('Erro ao adicionar insumo:', error);
toast.error('Erro ao adicionar insumo');
},
});
};