import { useState, useCallback } from 'react'; import { supabase } from '@/integrations/supabase/client'; import { Peca } from '@/hooks/usePecas'; import { useAuth } from '@/hooks/useAuth'; import { toast } from 'sonner'; export type TipoRevisao = 'simples' | 'critica'; export interface Revisao { id: string; of_number: string; peca_id: string; numero_revisao: string; tipo_revisao: TipoRevisao; descricao_motivo: string; observacoes: string | null; usuario_id: string | null; usuario_nome: string | null; usuario_email: string | null; data_revisao: string; dados_anteriores: Record; dados_novos: Record; campos_alterados: string[]; afeta_apontamentos: boolean; impacto_processos: Record | null; created_at: string; updated_at: string; peca?: { marca: string; etapa_fase: string; descricao: string; }; } export interface CriarRevisaoDTO { peca: Peca; numeroRevisao: string; tipoRevisao?: TipoRevisao; descricaoMotivo: string; observacoes?: string; novosDados: Partial; } export function useRevisoes(ofNumber?: string) { const { user } = useAuth(); const [revisoes, setRevisoes] = useState([]); const [loading, setLoading] = useState(false); // Carrega todas as revisões de uma OF const carregarRevisoesOF = useCallback(async (of?: string) => { const ofAlvo = of || ofNumber; if (!ofAlvo) return []; setLoading(true); try { const { data, error } = await supabase .from('revisoes' as any) .select(` *, peca:pecas(marca, etapa_fase, descricao) `) .eq('of_number', ofAlvo) .order('data_revisao', { ascending: false }); if (error) { console.error('Erro ao buscar revisões da OF:', error); throw error; } const lista = (data || []) as unknown as Revisao[]; setRevisoes(lista); return lista; } catch (err: any) { console.error('Erro ao carregar revisões:', err); toast.error('Erro ao carregar registros de revisões'); return []; } finally { setLoading(false); } }, [ofNumber]); // Carrega histórico de revisões de uma peça específica const carregarRevisoesPeca = useCallback(async (pecaId: string): Promise => { if (!pecaId) return []; try { const { data, error } = await supabase .from('revisoes' as any) .select(` *, peca:pecas(marca, etapa_fase, descricao) `) .eq('peca_id', pecaId) .order('data_revisao', { ascending: false }); if (error) { console.error('Erro ao buscar revisões da peça:', error); throw error; } return (data || []) as unknown as Revisao[]; } catch (err: any) { console.error('Erro ao carregar revisões da peça:', err); return []; } }, []); // Analisa automaticamente se uma alteração é Crítica ou Simples const detectarTipoRevisao = (pecaAtual: Peca, novosDados: Partial): { tipo: TipoRevisao; camposAlterados: string[]; afetaApontamentos: boolean; detalhes: string[]; } => { const camposCriticos = ['marca', 'quantidade', 'peso_unitario', 'peso_total', 'tem_componentes']; const camposAlterados: string[] = []; const detalhes: string[] = []; let isCritica = false; let afetaApontamentos = false; // Verificar cada campo relevante const chavesParaChecar = [ 'marca', 'quantidade', 'peso_unitario', 'peso_total', 'descricao', 'etapa_fase', 'material', 'perfil_principal', 'tratamento_superficial', 'comprimento', 'tem_componentes' ]; for (const chave of chavesParaChecar) { const valorAntigo = (pecaAtual as any)[chave]; const valorNovo = (novosDados as any)[chave]; if (valorNovo !== undefined && valorNovo !== null && valorNovo !== valorAntigo) { // Para números, checar diferença de valor if (typeof valorAntigo === 'number' && typeof valorNovo === 'number') { if (Math.abs(valorAntigo - valorNovo) > 0.0001) { camposAlterados.push(chave); if (camposCriticos.includes(chave)) { isCritica = true; if (chave === 'marca' || chave === 'quantidade') { afetaApontamentos = true; } } } } else { camposAlterados.push(chave); if (camposCriticos.includes(chave)) { isCritica = true; if (chave === 'marca') { afetaApontamentos = true; } } } } } if (camposAlterados.includes('marca')) { detalhes.push(`Alteração no código/marca da peça (${pecaAtual.marca} ➔ ${novosDados.marca})`); } if (camposAlterados.includes('quantidade')) { const diff = Number(novosDados.quantidade || 0) - Number(pecaAtual.quantidade || 0); detalhes.push(`Alteração na quantidade (${pecaAtual.quantidade} ➔ ${novosDados.quantidade}, dif: ${diff > 0 ? '+' : ''}${diff})`); } if (camposAlterados.includes('peso_unitario') || camposAlterados.includes('peso_total')) { detalhes.push(`Alteração dimensional/peso`); } return { tipo: isCritica ? 'critica' : 'simples', camposAlterados, afetaApontamentos, detalhes }; }; // Cria e aplica a revisão const criarRevisao = async (dto: CriarRevisaoDTO): Promise => { const { peca, numeroRevisao, descricaoMotivo, observacoes, novosDados } = dto; if (!dto.descricaoMotivo?.trim()) { toast.error('Informe o motivo ou observações da revisão'); return false; } const analise = detectarTipoRevisao(peca, novosDados); const tipoFinal = dto.tipoRevisao || analise.tipo; const camposAlterados = analise.camposAlterados; if (camposAlterados.length === 0) { toast.error('Nenhum campo foi alterado nesta peça.'); return false; } setLoading(true); try { const dadosAnteriores: Record = {}; const dadosNovos: Record = {}; camposAlterados.forEach(c => { dadosAnteriores[c] = (peca as any)[c]; dadosNovos[c] = (novosDados as any)[c]; }); const impactoProcessos: Record = { resumo: [], ajustes_apontamentos: [] }; // REGRA DE NEGÓCIO DE APONTAMENTOS / PROCESSOS: // Caso 1: A quantidade foi reduzida (pra menos) const novaQtd = Number(novosDados.quantidade !== undefined ? novosDados.quantidade : peca.quantidade); const qtdAntiga = Number(peca.quantidade || 0); if (camposAlterados.includes('quantidade') && novaQtd < qtdAntiga) { // Buscar todos os apontamentos da peça agrupados por processo const { data: apontamentos, error: apErr } = await supabase .from('apontamentos_producao') .select(` id, processo_id, quantidade_produzida, data_apontamento, created_at, processo:processos_fabricacao(id, nome, ordem) `) .eq('peca_id', peca.id) .eq('tipo_apontamento', 'peca') .order('created_at', { ascending: false }); if (!apErr && apontamentos && apontamentos.length > 0) { // Agrupar apontamentos por processo const porProcesso: Record = {}; apontamentos.forEach(a => { if (!porProcesso[a.processo_id]) porProcesso[a.processo_id] = []; porProcesso[a.processo_id].push(a); }); for (const [procId, listaAps] of Object.entries(porProcesso)) { const totalProduzido = listaAps.reduce((sum, a) => sum + Number(a.quantidade_produzida || 0), 0); const nomeProc = (listaAps[0].processo as any)?.nome || procId; if (totalProduzido > novaQtd) { let excesso = totalProduzido - novaQtd; impactoProcessos.resumo.push( `Processo "${nomeProc}": Produção de ${totalProduzido} un reduzida para o novo teto de ${novaQtd} un (corte de ${excesso} un).` ); // Reduzir dos apontamentos mais recentes para os mais antigos for (const ap of listaAps) { if (excesso <= 0) break; const apQtd = Number(ap.quantidade_produzida || 0); if (apQtd <= excesso) { // Apontamento inteiro é absorvido/removido await supabase .from('apontamentos_producao') .delete() .eq('id', ap.id); excesso -= apQtd; impactoProcessos.ajustes_apontamentos.push({ apontamento_id: ap.id, processo: nomeProc, acao: 'removido', quantidade_anterior: apQtd, quantidade_nova: 0 }); } else { // Apontamento parcial const novaQtdAp = apQtd - excesso; await supabase .from('apontamentos_producao') .update({ quantidade_produzida: novaQtdAp, observacoes: `${ap.data_apontamento || ''} [Revisado por ${numeroRevisao} - Qtd ajustada de ${apQtd} para ${novaQtdAp}]` }) .eq('id', ap.id); impactoProcessos.ajustes_apontamentos.push({ apontamento_id: ap.id, processo: nomeProc, acao: 'reduzido', quantidade_anterior: apQtd, quantidade_nova: novaQtdAp }); excesso = 0; } } } else { impactoProcessos.resumo.push( `Processo "${nomeProc}": Mantido (${totalProduzido} un já apontadas estão dentro do novo limite de ${novaQtd} un).` ); } } } } else if (camposAlterados.includes('quantidade') && novaQtd > qtdAntiga) { // Caso 2: A quantidade foi aumentada (pra mais) const diferenca = novaQtd - qtdAntiga; impactoProcessos.resumo.push( `Aumento de quantidade (+${diferenca} un): O saldo excedente nasce automaticamente no 1º processo da linha de fabricação para novos apontamentos.` ); } // Caso 3: Marca alterada if (camposAlterados.includes('marca')) { const novaMarca = String(novosDados.marca || ''); impactoProcessos.resumo.push( `Código da peça alterado de "${peca.marca}" para "${novaMarca}". Vínculos sincronizados.` ); // Atualizar romaneios que desnormalizam a marca await supabase .from('itens_romaneio_pecas') .update({ marca: novaMarca }) .eq('peca_id', peca.id); } // 1. Atualizar a peça no banco de dados const payloadUpdate: any = { ...novosDados, updated_at: new Date().toISOString() }; // Recalcular peso total se peso_unitario ou quantidade mudaram if (novosDados.quantidade !== undefined || novosDados.peso_unitario !== undefined) { const qtdFinal = Number(novosDados.quantidade !== undefined ? novosDados.quantidade : peca.quantidade); const pesoUnitFinal = Number(novosDados.peso_unitario !== undefined ? novosDados.peso_unitario : peca.peso_unitario); payloadUpdate.peso_total = Number((qtdFinal * pesoUnitFinal).toFixed(3)); } const { error: updateErr } = await supabase .from('pecas') .update(payloadUpdate) .eq('id', peca.id); if (updateErr) { console.error('Erro ao atualizar peça na revisão:', updateErr); throw updateErr; } // 2. Inserir na tabela revisoes const { error: revErr } = await supabase .from('revisoes' as any) .insert({ of_number: peca.of_number, peca_id: peca.id, numero_revisao: numeroRevisao.trim(), tipo_revisao: tipoFinal, descricao_motivo: descricaoMotivo.trim(), observacoes: observacoes?.trim() || null, usuario_id: user?.id || null, usuario_nome: user?.name || user?.email || 'Administrador', usuario_email: user?.email || null, dados_anteriores: dadosAnteriores, dados_novos: dadosNovos, campos_alterados: camposAlterados, afeta_apontamentos: analise.afetaApontamentos, impacto_processos: impactoProcessos, data_revisao: new Date().toISOString() }); if (revErr) { console.error('Erro ao registrar revisão:', revErr); throw revErr; } toast.success( `Revisão ${numeroRevisao} (${tipoFinal === 'critica' ? 'Crítica' : 'Simples'}) registrada com sucesso!` ); // Recarregar revisões da OF await carregarRevisoesOF(peca.of_number); return true; } catch (err: any) { console.error('Falha ao processar revisão:', err); toast.error('Erro ao processar revisão: ' + (err.message || 'Erro desconhecido')); return false; } finally { setLoading(false); } }; return { revisoes, loading, carregarRevisoesOF, carregarRevisoesPeca, detectarTipoRevisao, criarRevisao }; }