From 2bf0ccd8ceb919fc1a5278cd9a88567d4f6f3c96 Mon Sep 17 00:00:00 2001 From: admbrainsteel Date: Wed, 28 Oct 2026 10:33:03 -0300 Subject: [PATCH] fix(expedicao): isolar peca principal no seletor de romaneio sem somar componentes --- .../expedicao/ItensRomaneioModal.tsx | 147 ++++++++++++++---- src/hooks/useRomaneios.tsx | 15 +- 2 files changed, 129 insertions(+), 33 deletions(-) diff --git a/src/components/expedicao/ItensRomaneioModal.tsx b/src/components/expedicao/ItensRomaneioModal.tsx index 5eeeca3..6f8093b 100644 --- a/src/components/expedicao/ItensRomaneioModal.tsx +++ b/src/components/expedicao/ItensRomaneioModal.tsx @@ -28,7 +28,10 @@ interface PecaDisponivel { id: string; marca: string; descricao: string; + perfil_principal?: string | null; etapa_fase: string; + quantidade_total_peca: number; + quantidade_ja_expedida: number; quantidade_disponivel: number; peso_unitario: number; comprimento?: number | null; @@ -104,18 +107,24 @@ export const ItensRomaneioModal: React.FC = ({ .from('apontamentos_producao') .select(` peca_id, + componente_id, + tipo_apontamento, quantidade_produzida, processo_id, processo:processos_fabricacao!inner!apontamentos_producao_processo_id_fkey(nome), + componente:componentes_peca!apontamentos_producao_componente_id_fkey(id, quantidade_por_peca), peca:pecas!inner!apontamentos_producao_peca_id_fkey( id, marca, descricao, + perfil_principal, etapa_fase, + quantidade, peso_unitario, comprimento, prioridade, - of_number + of_number, + tem_componentes ) `) .eq('peca.of_number', romaneio.of_number) @@ -141,49 +150,99 @@ export const ItensRomaneioModal: React.FC = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any (expedidosData as any[]).forEach((item: any) => { const pid = item.peca_id; - const qtd = item.quantidade_expedida || 0; + const qtd = Number(item.quantidade_expedida) || 0; const current = expedidosMap.get(pid) || 0; expedidosMap.set(pid, current + qtd); }); } - // Agrupar por peça e calcular quantidade total produzida - const pecasAgrupadas = new Map(); - + // 3. Agrupar por peça principal (NUNCA somar unidades de subcomponentes na peça principal) + const pecasAgrupadas = new Map< + string, + { + peca: any; + qtdPecaDireta: number; + componentesCortados: Map; + } + >(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any (apontamentosData as any[])?.forEach((apontamento: any) => { - const pecaId = apontamento.peca?.id; + const pecaObj = apontamento.peca; + const pecaId = pecaObj?.id; if (!pecaId) return; - const quantidade = apontamento.quantidade_produzida || 0; - - if (pecasAgrupadas.has(pecaId)) { - const pecaExistente = pecasAgrupadas.get(pecaId)!; - pecaExistente.quantidade_disponivel += quantidade; // Aqui salva o Bruto temporariamente - } else { + if (!pecasAgrupadas.has(pecaId)) { pecasAgrupadas.set(pecaId, { - id: apontamento.peca.id, - marca: apontamento.peca.marca, - descricao: apontamento.peca.descricao || '', - etapa_fase: apontamento.peca.etapa_fase || '', - quantidade_disponivel: quantidade, // Bruto temporário - peso_unitario: apontamento.peca.peso_unitario || 0, - comprimento: apontamento.peca.comprimento || null, - prioridade: apontamento.peca.prioridade || 'P4' + peca: pecaObj, + qtdPecaDireta: 0, + componentesCortados: new Map() }); } + + const grupo = pecasAgrupadas.get(pecaId)!; + const quantidade = Number(apontamento.quantidade_produzida) || 0; + const isComponente = + apontamento.tipo_apontamento === 'componente' || Boolean(apontamento.componente_id); + + if (isComponente) { + const compId = apontamento.componente_id || 'comp_default'; + const porPeca = Math.max(1, Number(apontamento.componente?.quantidade_por_peca) || 1); + const atualComp = grupo.componentesCortados.get(compId)?.cortado || 0; + grupo.componentesCortados.set(compId, { + cortado: atualComp + quantidade, + porPeca + }); + } else { + grupo.qtdPecaDireta += quantidade; + } }); - // 4. Calcular o saldo Real (Produzido - Expedido) e limpar zerados + // 4. Calcular o saldo Real da peça principal (Produzido da Peça Principal - Expedido em Romaneios) const pecasDisponiveisFinal: PecaDisponivel[] = []; - pecasAgrupadas.forEach((peca, pecaId) => { + pecasAgrupadas.forEach((grupo, pecaId) => { + const pecaObj = grupo.peca; + const qtdTotalPeca = Math.max(1, Number(pecaObj.quantidade) || 1); + + let qtdProduzidaPecaPrincipal = 0; + if (grupo.qtdPecaDireta > 0) { + // Apontamento direto da peça principal (ou kit sincronizado) + qtdProduzidaPecaPrincipal = Math.min(qtdTotalPeca, grupo.qtdPecaDireta); + } else if (grupo.componentesCortados.size > 0) { + // Caso haja apenas apontamentos de subcomponentes no processo (ex: Corte antes da soldagem), + // converte para a quantidade equivalente da peça principal (limitada à quantidade total da peça na OF) + let maxPecasEquivalentes = 0; + grupo.componentesCortados.forEach(({ cortado, porPeca }) => { + const equivalentes = Math.floor(cortado / porPeca); + if (equivalentes > maxPecasEquivalentes) { + maxPecasEquivalentes = equivalentes; + } + }); + qtdProduzidaPecaPrincipal = Math.min( + qtdTotalPeca, + maxPecasEquivalentes > 0 ? maxPecasEquivalentes : qtdTotalPeca + ); + } + const qtdExpedida = expedidosMap.get(pecaId) || 0; - const saldoReal = peca.quantidade_disponivel - qtdExpedida; - + const saldoReal = Math.min( + qtdTotalPeca - qtdExpedida, + qtdProduzidaPecaPrincipal - qtdExpedida + ); + if (saldoReal > 0) { pecasDisponiveisFinal.push({ - ...peca, - quantidade_disponivel: saldoReal // Retorna apenas o saldo disponível + id: pecaObj.id, + marca: pecaObj.marca, + descricao: pecaObj.descricao || '', + perfil_principal: pecaObj.perfil_principal || null, + etapa_fase: pecaObj.etapa_fase || '', + quantidade_total_peca: qtdTotalPeca, + quantidade_ja_expedida: qtdExpedida, + quantidade_disponivel: saldoReal, + peso_unitario: Number(pecaObj.peso_unitario) || 0, + comprimento: pecaObj.comprimento || null, + prioridade: pecaObj.prioridade || 'P4' }); } }); @@ -344,6 +403,7 @@ export const ItensRomaneioModal: React.FC = ({ quantidade_expedida: peca.quantidade_disponivel, peso_unitario: peca.peso_unitario, peso_total: peca.quantidade_disponivel * peca.peso_unitario, + comprimento: peca.comprimento || null, marca: peca.marca, descricao: peca.descricao, fase: peca.etapa_fase @@ -400,6 +460,7 @@ export const ItensRomaneioModal: React.FC = ({ quantidade_expedida: quantidade, peso_unitario: pecaSelecionada.peso_unitario, peso_total: quantidade * pecaSelecionada.peso_unitario, + comprimento: pecaSelecionada.comprimento || null, marca: pecaSelecionada.marca, descricao: pecaSelecionada.descricao, fase: pecaSelecionada.etapa_fase @@ -629,7 +690,16 @@ export const ItensRomaneioModal: React.FC = ({ - { + setPecaDisponivel(val); + const pSel = pecasDisponiveis.find((p) => p.id === val); + if (pSel) { + setQuantidadePeca(String(pSel.quantidade_disponivel)); + } + }} + > @@ -646,13 +716,30 @@ export const ItensRomaneioModal: React.FC = ({ pecasFiltradas.map((peca) => { const limite = romaneio.comprimento_maximo_veiculo || 12000; const naoCabe = Boolean(peca.comprimento && peca.comprimento > limite); + const descClean = (peca.descricao || '').trim(); + const perfilClean = (peca.perfil_principal || '').trim(); + const descricaoCompleta = + descClean && perfilClean && descClean.toLowerCase() !== perfilClean.toLowerCase() + ? `${descClean} - ${perfilClean}` + : descClean || perfilClean || 'Peça Principal'; + const faseFormatada = peca.etapa_fase + ? peca.etapa_fase.toLowerCase().startsWith('fase') + ? peca.etapa_fase + : `Fase ${peca.etapa_fase}` + : ''; + const infoSaldo = + peca.quantidade_ja_expedida > 0 + ? `Disp: ${peca.quantidade_disponivel} de ${peca.quantidade_total_peca} un` + : `Qtd Disp: ${peca.quantidade_disponivel} un`; + return (
- - {peca.marca} - {peca.descricao} (Qtd: {peca.quantidade_disponivel}) + + {peca.marca} + {faseFormatada ? ` [${faseFormatada}]` : ''} - {descricaoCompleta} ({infoSaldo}) - {peca.comprimento && ( + {peca.comprimento != null && Number(peca.comprimento) > 0 && ( diff --git a/src/hooks/useRomaneios.tsx b/src/hooks/useRomaneios.tsx index 46d198b..c1963e1 100644 --- a/src/hooks/useRomaneios.tsx +++ b/src/hooks/useRomaneios.tsx @@ -128,12 +128,15 @@ export const usePecasPintura = (ofNumber?: string) => { .from('apontamentos_producao') .select(` peca_id, + componente_id, + tipo_apontamento, quantidade_produzida, pecas ( id, marca, etapa_fase, descricao, + quantidade, peso_unitario, comprimento, of_number @@ -151,15 +154,16 @@ export const usePecasPintura = (ofNumber?: string) => { 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) || []); + .in('peca_id', apontamentosPintura?.map(a => a.peca_id).filter(Boolean) || []); if (expedidosError) throw expedidosError; - // Calcular disponibilidade + // 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; @@ -173,6 +177,7 @@ export const usePecasPintura = (ofNumber?: string) => { 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, @@ -186,10 +191,14 @@ export const usePecasPintura = (ofNumber?: string) => { ?.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: peca.quantidade_produzida_pintura - totalExpedido + quantidade_disponivel: qtdProdLimitada - totalExpedido }); });