Files
tracksteel_app/src/utils/validacaoRomaneio.ts
T

371 lines
12 KiB
TypeScript

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<ResultadoValidacaoEntrega> {
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<string, Map<string, number>>();
(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<string, number>());
}
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<string, number>();
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;
}
}