import { supabase } from '@/integrations/supabase/client'; import { ItemPendenciaProducao } from '@/components/expedicao/PendenciasProducaoModal'; export interface ProcessoFaltanteItem { processo_id: string; nome: string; ordem: number; qtdFaltante: number; qtdFeita: number; qtdNecessaria: number; } export interface PecaAnaliseRomaneio { peca_id: string; marca: string; fase?: string; quantidade_expedida: number; tem_componentes: boolean; processosFaltantes: ProcessoFaltanteItem[]; liberada: boolean; qtdJaExpedida: number; } export interface ResultadoValidacaoEntrega { valido: boolean; mensagem?: string; pendencias?: ItemPendenciaProducao[]; pecasAnalise?: PecaAnaliseRomaneio[]; totalItens?: number; totalLiberadas?: number; totalPendentes?: number; } /** * Valida e analisa se um romaneio pode ser marcado com o status "Entregue". * Identifica para cada peça do romaneio se todos os processos anteriores foram concluídos, * e detalha com precisão cirúrgica quais processos estão faltantes. */ export async function validarRomaneioParaEntrega( romaneioId?: string, ofNumber?: string ): Promise { if (!romaneioId) { return { valido: false, mensagem: 'Não é possível criar um novo romaneio diretamente com o status "Entregue". Crie o romaneio primeiro em planejamento, adicione as peças e confirme a entrega.' }; } try { // 1. Buscar peças cadastradas no romaneio const { data: itens, error: itensError } = await supabase .from('itens_romaneio_pecas') .select('id, peca_id, marca, quantidade_expedida, fase') .eq('romaneio_id', romaneioId); if (itensError) { console.error('❌ Erro ao buscar peças do romaneio:', itensError); return { valido: false, mensagem: 'Erro ao consultar peças do romaneio: ' + itensError.message }; } if (!itens || itens.length === 0) { return { valido: false, mensagem: 'O romaneio não possui nenhuma peça cadastrada. Adicione as peças antes de alterar o status para "Entregue".' }; } // 2. Buscar processos de fabricação ativos ordenados por ordem const { data: processos, error: procError } = await supabase .from('processos_fabricacao') .select('id, nome, ordem') .eq('ativo', true) .order('ordem'); if (procError || !processos) { console.error('❌ Erro ao buscar processos de fabricação:', procError); return { valido: false, mensagem: 'Erro ao consultar processos de fabricação.' }; } // Processo de Expedição (ordem 5 ou nome contendo 'expedi') const processoExpedicao = processos.find(p => p.nome?.toLowerCase().includes('expedi') || p.ordem === 5 ) || { id: '', nome: 'Expedicao', ordem: 5 }; // Processos anteriores que precisam estar concluídos const processosAnteriores = processos.filter(p => p.ordem < processoExpedicao.ordem); // 3. Buscar dados das peças (para saber se tem_componentes) const pecaIds = Array.from(new Set(itens.map(i => i.peca_id).filter(Boolean))); const { data: pecasInfo, error: pecasError } = await supabase .from('pecas') .select('id, marca, tem_componentes, fase') .in('id', pecaIds); if (pecasError) { console.error('❌ Erro ao buscar especificações das peças:', pecasError); } const pecasMap = new Map((pecasInfo || []).map(p => [p.id, p])); // 4. Buscar apontamentos existentes para essas peças let queryApontamentos = supabase .from('apontamentos_producao') .select('peca_id, processo_id, quantidade_produzida, tipo_apontamento') .in('peca_id', pecaIds); if (ofNumber) { queryApontamentos = queryApontamentos.eq('of_number', ofNumber); } const { data: apontamentos, error: aptError } = await queryApontamentos; if (aptError) { console.error('❌ Erro ao buscar apontamentos:', aptError); } // Mapear quantidades apontadas por peça mestre: peca_id -> processo_id -> soma(quantidade_produzida) const apontamentosMap = new Map>(); (apontamentos || []).forEach(apt => { // Ignorar apontamento de subcomponente para não inflar a contagem da peça mestre if (apt.tipo_apontamento && apt.tipo_apontamento !== 'peca') return; if (!apt.peca_id || !apt.processo_id) return; if (!apontamentosMap.has(apt.peca_id)) { apontamentosMap.set(apt.peca_id, new Map()); } const procMap = apontamentosMap.get(apt.peca_id)!; const atual = procMap.get(apt.processo_id) || 0; procMap.set(apt.processo_id, atual + Number(apt.quantidade_produzida || 0)); }); // 5. Analisar cada item do romaneio const pendencias: ItemPendenciaProducao[] = []; const pecasAnalise: PecaAnaliseRomaneio[] = []; itens.forEach(item => { const peca = pecasMap.get(item.peca_id); const temComp = peca?.tem_componentes ?? false; const procMap = apontamentosMap.get(item.peca_id) || new Map(); const qtdRomaneio = Number(item.quantidade_expedida || 0); const processosFaltantes: ProcessoFaltanteItem[] = []; for (const proc of processosAnteriores) { // Se for Solda e a peça NÃO tem componentes (peça simples sem montagem), não passa por solda const isSolda = proc.ordem === 3 || proc.nome.toLowerCase().includes('solda'); if (isSolda && !temComp) { continue; } const qtdFeita = procMap.get(proc.id) || 0; if (qtdFeita < qtdRomaneio) { const qtdFaltante = Math.max(0, qtdRomaneio - qtdFeita); processosFaltantes.push({ processo_id: proc.id, nome: proc.nome, ordem: proc.ordem, qtdNecessaria: qtdRomaneio, qtdFeita, qtdFaltante }); pendencias.push({ peca_id: item.peca_id, marca: item.marca || peca?.marca || 'Peça', processo: proc.nome, ordem: proc.ordem, qtdNecessaria: qtdRomaneio, qtdApontada: qtdFeita, qtdFaltante }); } } const qtdJaExpedida = procMap.get(processoExpedicao.id) || 0; pecasAnalise.push({ peca_id: item.peca_id, marca: item.marca || peca?.marca || 'Peça', fase: item.fase || peca?.fase || '-', quantidade_expedida: qtdRomaneio, tem_componentes: temComp, processosFaltantes, liberada: processosFaltantes.length === 0, qtdJaExpedida }); }); const totalLiberadas = pecasAnalise.filter(p => p.liberada).length; const totalPendentes = pecasAnalise.filter(p => !p.liberada).length; return { valido: pendencias.length === 0, pendencias, pecasAnalise, totalItens: pecasAnalise.length, totalLiberadas, totalPendentes }; } catch (err: any) { console.error('❌ Exceção ao validar romaneio para entrega:', err); return { valido: false, mensagem: 'Erro inesperado ao validar romaneio: ' + (err?.message || 'Falha de comunicação') }; } } /** * Executa o apontamento automático para o romaneio entregue: * - Grava na Data Real informada pelo usuário. * - Suporta apontamento em cascata nos processos anteriores pendentes (se autorizado pelo usuário). * - Registra apontamento oficial no processo de Expedição. * - Atualiza o status do romaneio para "Entregue". */ export async function executarApontamentoRomaneioEntregue({ romaneioId, ofNumber, numeroRomaneio, dataEntregaReal, autorizarCascata = false, apontarApenasLiberadas = false, userId }: { romaneioId: string; ofNumber: string; numeroRomaneio: string; dataEntregaReal: string; autorizarCascata?: boolean; apontarApenasLiberadas?: boolean; userId?: string; }): Promise<{ sucesso: boolean; mensagem: string; totalApontamentos: number; pecasProcessadas: string[] }> { try { const analise = await validarRomaneioParaEntrega(romaneioId, ofNumber); if (!analise.pecasAnalise || analise.pecasAnalise.length === 0) { throw new Error(analise.mensagem || 'Nenhuma peça encontrada no romaneio'); } // Buscar processo de Expedição const { data: processos } = await supabase .from('processos_fabricacao') .select('id, nome, ordem') .eq('ativo', true) .order('ordem'); const processoExpedicao = processos?.find(p => p.nome?.toLowerCase().includes('expedi') || p.ordem === 5 ); if (!processoExpedicao) { throw new Error('Processo Expedição não encontrado'); } const apontamentosParaInserir: any[] = []; const pecasProcessadas: string[] = []; const obsOrigem = `Apontamento via Romaneio ${numeroRomaneio} (ID: ${romaneioId})`; for (const peca of analise.pecasAnalise) { // Cenário 1: Peça com processos anteriores pendentes if (!peca.liberada) { if (autorizarCascata) { // Criar apontamento para cada processo faltante for (const faltante of peca.processosFaltantes) { apontamentosParaInserir.push({ of_number: ofNumber, peca_id: peca.peca_id, processo_id: faltante.processo_id, quantidade_produzida: faltante.qtdFaltante, data_apontamento: dataEntregaReal, observacoes: `Apontamento em cascata (${faltante.nome}) - ${obsOrigem}`, tipo_apontamento: 'peca', created_by: userId }); } // Criar apontamento de Expedição apontamentosParaInserir.push({ of_number: ofNumber, peca_id: peca.peca_id, processo_id: processoExpedicao.id, quantidade_produzida: peca.quantidade_expedida, data_apontamento: dataEntregaReal, observacoes: `Apontamento de expedição - ${obsOrigem}`, tipo_apontamento: 'peca', created_by: userId }); pecasProcessadas.push(`${peca.marca} (${peca.quantidade_expedida} un - com cascata)`); } else if (apontarApenasLiberadas) { // Pular peça pendente continue; } else { // Não autorizado e não pediu para apontar parciais continue; } } else { // Cenário 2: Peça 100% liberada nos processos anteriores apontamentosParaInserir.push({ of_number: ofNumber, peca_id: peca.peca_id, processo_id: processoExpedicao.id, quantidade_produzida: peca.quantidade_expedida, data_apontamento: dataEntregaReal, observacoes: `Apontamento de expedição - ${obsOrigem}`, tipo_apontamento: 'peca', created_by: userId }); pecasProcessadas.push(`${peca.marca} (${peca.quantidade_expedida} un)`); } } if (apontamentosParaInserir.length === 0) { return { sucesso: false, mensagem: 'Nenhum apontamento foi inserido pois não houve peças autorizadas para expedição.', totalApontamentos: 0, pecasProcessadas: [] }; } // Inserir os apontamentos na tabela oficial const { error: insertError } = await supabase .from('apontamentos_producao') .insert(apontamentosParaInserir); if (insertError) { console.error('❌ Erro ao inserir apontamentos:', insertError); throw new Error(`Falha ao registrar apontamentos: ${insertError.message}`); } // Atualizar o status do romaneio para "Entregue" await supabase .from('romaneios_expedicao') .update({ status: 'Entregue', updated_at: new Date().toISOString() }) .eq('id', romaneioId); // Registrar log em logs_apontamentos_automaticos await supabase .from('logs_apontamentos_automaticos') .insert({ romaneio_id: romaneioId, numero_romaneio: numeroRomaneio, of_number: ofNumber, total_pecas_processadas: pecasProcessadas.length, status_operacao: 'sucesso', usuario_executou: userId || null }); return { sucesso: true, mensagem: `${apontamentosParaInserir.length} apontamento(s) realizado(s) com sucesso na data ${dataEntregaReal}!`, totalApontamentos: apontamentosParaInserir.length, pecasProcessadas }; } catch (err: any) { console.error('❌ Erro na execução de apontamentos do romaneio:', err); throw err; } }