diff --git a/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx b/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx index dc61b51..f7eb34d 100644 --- a/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx +++ b/src/pages/smart/components/SmartConsultaRelatoriosFlow.tsx @@ -46,6 +46,7 @@ interface PecaStatus { pintura: number; expedido: number; montado_obra: number; + tem_componentes?: boolean | null; } export const SmartConsultaRelatoriosFlow: React.FC = ({ @@ -81,7 +82,7 @@ export const SmartConsultaRelatoriosFlow: React.FC + {peca.tem_componentes === false && ( + + S/M + + )} + {peca.perfil_principal && ( {peca.perfil_principal} @@ -905,14 +913,30 @@ export const SmartConsultaRelatoriosFlow: React.FC {[ - { nome: 'Det.', valor: peca.detalhamento }, - { nome: 'Corte', valor: peca.corte }, - { nome: 'Mont.', valor: peca.montagem }, - { nome: 'Solda', valor: peca.solda }, - { nome: 'Pint.', valor: peca.pintura }, - { nome: 'Emb.', valor: peca.expedido }, - { nome: 'Obra', valor: peca.montado_obra }, + { nome: 'Det.', valor: peca.detalhamento, isPula: false }, + { nome: 'Corte', valor: peca.corte, isPula: false }, + { nome: 'Mont.', valor: peca.montagem, isPula: peca.tem_componentes === false }, + { nome: 'Solda', valor: peca.solda, isPula: peca.tem_componentes === false }, + { nome: 'Pint.', valor: peca.pintura, isPula: false }, + { nome: 'Emb.', valor: peca.expedido, isPula: false }, + { nome: 'Obra', valor: peca.montado_obra, isPula: false }, ].map((etapa, idx) => { + if (etapa.isPula) { + return ( +
+
+ {etapa.nome} +
+
+ S/M +
+ + N/A + +
+ ); + } + const badge = getBadgeEtapa(etapa.valor, peca.quantidade); return (
diff --git a/src/pages/smart/components/SmartEmbarqueFlow.tsx b/src/pages/smart/components/SmartEmbarqueFlow.tsx index 9a4f4eb..63e3cf4 100644 --- a/src/pages/smart/components/SmartEmbarqueFlow.tsx +++ b/src/pages/smart/components/SmartEmbarqueFlow.tsx @@ -55,6 +55,7 @@ interface ItemRomaneioCarregado { quantidade_expedida: number; peso_unitario: number; peso_total: number; + tem_componentes?: boolean | null; created_at?: string; } @@ -67,6 +68,7 @@ interface PecaPronta { peso_unitario: number; quantidadeJaExpedida: number; saldoDisponivel: number; + tem_componentes?: boolean | null; } type SubTelaEmbarque = @@ -159,7 +161,7 @@ export const SmartEmbarqueFlow: React.FC = ({ obra, onVo const [resPecas, resItensExpedidos] = await Promise.all([ supabase .from('pecas') - .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario') + .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, tem_componentes') .eq('of_number', obra.of_number), supabase .from('itens_romaneio_pecas') @@ -982,9 +984,16 @@ export const SmartEmbarqueFlow: React.FC = ({ obra, onVo {pecaEmbarque ? (
- - {pecaEmbarque.marca} - +
+ + {pecaEmbarque.marca} + + {pecaEmbarque.tem_componentes === false && ( + + S/M + + )} +
Saldo Disponível: {pecaEmbarque.saldoDisponivel} un @@ -1081,6 +1090,11 @@ export const SmartEmbarqueFlow: React.FC = ({ obra, onVo
{p.marca} + {p.tem_componentes === false && ( + + S/M + + )} {p.etapa_fase || 'Estrutural'} @@ -1266,6 +1280,11 @@ export const SmartEmbarqueFlow: React.FC = ({ obra, onVo {item.marca} + {(item.tem_componentes === false || pecasProntas.some(p => p.id === item.peca_id && p.tem_componentes === false)) && ( + + S/M + + )} {item.fase || '1'} diff --git a/src/pages/smart/components/SmartMontagemFlow.tsx b/src/pages/smart/components/SmartMontagemFlow.tsx index cc679c4..1ba8c28 100644 --- a/src/pages/smart/components/SmartMontagemFlow.tsx +++ b/src/pages/smart/components/SmartMontagemFlow.tsx @@ -28,6 +28,7 @@ interface ItemMontagem { quantidade_expedida: number; quantidade_ja_apontada: number; saldo_disponivel: number; + tem_componentes?: boolean | null; } export const SmartMontagemFlow: React.FC = ({ @@ -50,24 +51,41 @@ export const SmartMontagemFlow: React.FC = ({ try { setLoading(true); - // 1. Buscar peças expedidas em romaneios desta OF - const { data: pecasExpedidas, error: errorExp } = await supabase - .from('itens_romaneio_pecas') - .select(` - marca, - descricao, - peso_unitario, - quantidade_expedida, - romaneios_expedicao!inner( - of_number - ) - `) - .eq('romaneios_expedicao.of_number', obra.of_number); + // Buscar peças expedidas, apontamentos RDO e info de componentes em paralelo + const [resExp, resRDO, resPecas] = await Promise.all([ + supabase + .from('itens_romaneio_pecas') + .select(` + marca, + descricao, + peso_unitario, + quantidade_expedida, + romaneios_expedicao!inner( + of_number + ) + `) + .eq('romaneios_expedicao.of_number', obra.of_number), + supabase + .from('apontamentos_peca_obra') + .select(` + marca_peca, + quantidade, + diario_obra_rdo!inner( + of_number + ) + `) + .eq('diario_obra_rdo.of_number', obra.of_number), + supabase + .from('pecas') + .select('marca, tem_componentes') + .eq('of_number', obra.of_number), + ]); - if (errorExp) throw errorExp; + if (resExp.error) throw resExp.error; + if (resRDO.error) throw resRDO.error; const mapaExpedidas = new Map(); - (pecasExpedidas || []).forEach((item: any) => { + (resExp.data || []).forEach((item: any) => { const atual = mapaExpedidas.get(item.marca) || { marca: item.marca, descricao: item.descricao || '', @@ -78,26 +96,19 @@ export const SmartMontagemFlow: React.FC = ({ mapaExpedidas.set(item.marca, atual); }); - // 2. Buscar apontamentos já feitos no RDO para esta OF - const { data: apontamentosRDO, error: errorRDO } = await supabase - .from('apontamentos_peca_obra') - .select(` - marca_peca, - quantidade, - diario_obra_rdo!inner( - of_number - ) - `) - .eq('diario_obra_rdo.of_number', obra.of_number); - - if (errorRDO) throw errorRDO; - const mapaMontadas = new Map(); - (apontamentosRDO || []).forEach((ap: any) => { + (resRDO.data || []).forEach((ap: any) => { const atual = mapaMontadas.get(ap.marca_peca) || 0; mapaMontadas.set(ap.marca_peca, atual + Number(ap.quantidade || 0)); }); + const mapaSM = new Map(); + (resPecas.data || []).forEach((p: any) => { + if (p.tem_componentes === false) { + mapaSM.set(p.marca, true); + } + }); + // 3. Montar lista de saldos const lista: ItemMontagem[] = Array.from(mapaExpedidas.values()).map((exp) => { const jaMontado = mapaMontadas.get(exp.marca) || 0; @@ -109,6 +120,7 @@ export const SmartMontagemFlow: React.FC = ({ quantidade_expedida: exp.expedido, quantidade_ja_apontada: jaMontado, saldo_disponivel: saldo, + tem_componentes: mapaSM.has(exp.marca) ? false : true, }; }); @@ -218,9 +230,16 @@ export const SmartMontagemFlow: React.FC = ({ {/* Resumo da Peça */}
- - {itemSelecionado.marca} - +
+ + {itemSelecionado.marca} + + {itemSelecionado.tem_componentes === false && ( + + S/M + + )} +
Montagem em Obra @@ -388,9 +407,16 @@ export const SmartMontagemFlow: React.FC = ({ >
- - {item.marca} - +
+ + {item.marca} + + {item.tem_componentes === false && ( + + S/M + + )} +
{item.descricao || 'Peça Estrutural'}
diff --git a/src/pages/smart/components/SmartProducaoFlow.tsx b/src/pages/smart/components/SmartProducaoFlow.tsx index bd8fb2d..874ecc4 100644 --- a/src/pages/smart/components/SmartProducaoFlow.tsx +++ b/src/pages/smart/components/SmartProducaoFlow.tsx @@ -38,8 +38,80 @@ interface PecaData { quantidade: number; peso_unitario: number; perfil_principal?: string; + tem_componentes?: boolean | null; } +interface CalculoSaldoPeca { + saldo: number; + jaProduzido: number; + qtdDisponivelParaEntrar: number; + ehSemMontagem: boolean; + motivoBloqueio: string | null; +} + +// Função auxiliar pura para cálculo sequencial de saldo por processo e regra S/M +export const calcularSaldoProcessoPeca = ( + peca: PecaData, + processoAtual: ProcessoFabricacao, + todosProcessos: ProcessoFabricacao[], + mapaProd: Map +): CalculoSaldoPeca => { + const ehSemMontagem = peca.tem_componentes === false; + const keyAtual = `${peca.id}_${processoAtual.id}`; + const jaProduzidoAtual = mapaProd.get(keyAtual) || 0; + + const procsOrdenados = [...todosProcessos].sort((a, b) => a.ordem - b.ordem); + const indexAtual = procsOrdenados.findIndex((p) => p.id === processoAtual.id); + const nomeProcAtual = (processoAtual.nome || '').toLowerCase(); + + const ehSolda = nomeProcAtual.includes('solda'); + const ehMontagem = nomeProcAtual.includes('montag'); + + let qtdDisponivelParaEntrar = Number(peca.quantidade) || 0; + let motivoBloqueio: string | null = null; + + // REGRA 1: Peças sem montagem (S/M) não passam por Montagem nem por Solda + if (ehSemMontagem && (ehSolda || ehMontagem)) { + qtdDisponivelParaEntrar = 0; + motivoBloqueio = 'Peça S/M (Pula Montagem e Solda)'; + } else if (indexAtual > 0) { + // REGRA 2: Encontrar processo anterior válido + let procAnteriorValido: ProcessoFabricacao | null = null; + for (let i = indexAtual - 1; i >= 0; i--) { + const proc = procsOrdenados[i]; + const nomeP = proc.nome.toLowerCase(); + // Se a peça for S/M, pula solda e montagem na busca do anterior (ex: Corte -> Pintura) + if (ehSemMontagem && (nomeP.includes('solda') || nomeP.includes('montag'))) { + continue; + } + procAnteriorValido = proc; + break; + } + + if (procAnteriorValido) { + const keyAnt = `${peca.id}_${procAnteriorValido.id}`; + const qtdApontadaAnterior = mapaProd.get(keyAnt) || 0; + qtdDisponivelParaEntrar = qtdApontadaAnterior; + + if (qtdApontadaAnterior === 0) { + motivoBloqueio = `Aguardando ${procAnteriorValido.nome}`; + } else if (qtdApontadaAnterior < peca.quantidade && qtdApontadaAnterior <= jaProduzidoAtual) { + motivoBloqueio = `Aguardando saldo em ${procAnteriorValido.nome} (${qtdApontadaAnterior}/${peca.quantidade})`; + } + } + } + + const saldo = Math.max(0, qtdDisponivelParaEntrar - jaProduzidoAtual); + + return { + saldo, + jaProduzido: jaProduzidoAtual, + qtdDisponivelParaEntrar, + ehSemMontagem, + motivoBloqueio, + }; +}; + interface ApontamentoItem { peca_id: string; processo_id: string; @@ -77,7 +149,7 @@ export const SmartProducaoFlow: React.FC = ({ const [resPecas, resApontamentos] = await Promise.all([ supabase .from('pecas') - .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, perfil_principal') + .select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, perfil_principal, tem_componentes') .eq('of_number', obra.of_number), supabase .from('apontamentos_producao') @@ -126,7 +198,7 @@ export const SmartProducaoFlow: React.FC = ({ return mapa; }, [apontamentosExistentes]); - // Peças filtradas pela fase selecionada com cálculo do saldo + // Peças filtradas pela fase selecionada com cálculo estrito de precedência de processos e regra S/M const pecasComSaldo = useMemo(() => { if (!processoSelecionado) return []; @@ -136,22 +208,19 @@ export const SmartProducaoFlow: React.FC = ({ return p.etapa_fase?.trim() === faseSelecionada; }) .map((p) => { - const key = `${p.id}_${processoSelecionado.id}`; - const jaProduzido = mapaProducao.get(key) || 0; - const saldo = Math.max(0, p.quantidade - jaProduzido); + const info = calcularSaldoProcessoPeca(p, processoSelecionado, processos, mapaProducao); return { ...p, - jaProduzido, - saldo, + ...info, }; }) .sort((a, b) => { - // Colocar primeiro as que têm saldo pendente + // Colocar primeiro as que têm saldo pendente disponível > 0 if (a.saldo > 0 && b.saldo === 0) return -1; if (a.saldo === 0 && b.saldo > 0) return 1; return a.marca.localeCompare(b.marca, undefined, { numeric: true, sensitivity: 'base' }); }); - }, [pecas, faseSelecionada, processoSelecionado, mapaProducao]); + }, [pecas, faseSelecionada, processoSelecionado, mapaProducao, processos]); // Helper de ícone por processo const getIconeProcesso = (nome: string) => { @@ -488,6 +557,14 @@ export const SmartProducaoFlow: React.FC = ({ {item.marca} + {item.tem_componentes === false && ( + + S/M + + )} {item.perfil_principal && ( {item.perfil_principal} @@ -497,6 +574,12 @@ export const SmartProducaoFlow: React.FC = ({
{item.descricao || 'Peça Estrutural'}
+ {item.motivoBloqueio && item.saldo === 0 && !concluida && ( +
+ + {item.motivoBloqueio} +
+ )}
@@ -505,9 +588,13 @@ export const SmartProducaoFlow: React.FC = ({ 100% Pronto - ) : ( + ) : item.saldo > 0 ? ( - Falta: {item.saldo} + Disponível: {item.saldo} + + ) : ( + + Pendente ant. )}
@@ -529,18 +616,24 @@ export const SmartProducaoFlow: React.FC = ({
- {!concluida && ( + {!concluida && item.saldo > 0 && ( @@ -581,9 +674,13 @@ export const SmartProducaoFlow: React.FC = ({ // RENDER: ETAPA 1.4 — APONTAMENTO DA QUANTIDADE (TECLADO TOUCH + INPUT DIRETO) // ───────────────────────────────────────────────────────────── if (etapaAtual === 'quantidade' && pecaSelecionada && processoSelecionado) { - const key = `${pecaSelecionada.id}_${processoSelecionado.id}`; - const jaProduzido = mapaProducao.get(key) || 0; - const saldoPendente = Math.max(0, pecaSelecionada.quantidade - jaProduzido); + const infoSaldoAtual = calcularSaldoProcessoPeca( + pecaSelecionada, + processoSelecionado, + processos, + mapaProducao + ); + const saldoPendente = infoSaldoAtual.saldo; // Ajuste por incremento const somarQtd = (valor: number) => { @@ -651,9 +748,19 @@ export const SmartProducaoFlow: React.FC = ({ {/* Resumo da Peça no Topo */}
- - {pecaSelecionada.marca} - +
+ + {pecaSelecionada.marca} + + {pecaSelecionada.tem_componentes === false && ( + + S/M + + )} +
{processoSelecionado.nome}