fix: isolar componentes e aplicar trava de teto na disponibilidade do Modo Smart

This commit is contained in:
2026-10-20 11:05:25 -03:00
parent 9745964837
commit 264c41cc80
5 changed files with 33 additions and 12 deletions
-2
View File
@@ -1,2 +0,0 @@
// Script temporário de auditoria da OF B134
export {};
+6 -2
View File
@@ -110,11 +110,11 @@ export default function Visualizador3D() {
.select('id, of_number, etapa_fase, marca, descricao, quantidade, peso_unitario, peso_total, perfil_principal, material') .select('id, of_number, etapa_fase, marca, descricao, quantidade, peso_unitario, peso_total, perfil_principal, material')
.eq('of_number', selectedOF); .eq('of_number', selectedOF);
// Fetch apontamentos com join de peca e processo // Fetch apontamentos com join de peca e processo (apenas peças mestres)
const { data: apontamentosData, error: errorApontamentos } = await supabase const { data: apontamentosData, error: errorApontamentos } = await supabase
.from('apontamentos_producao' as any) .from('apontamentos_producao' as any)
.select(` .select(`
id, of_number, peca_id, quantidade_produzida, id, of_number, peca_id, quantidade_produzida, tipo_apontamento, componente_id,
peca:pecas!apontamentos_producao_peca_id_fkey(marca, etapa_fase), peca:pecas!apontamentos_producao_peca_id_fkey(marca, etapa_fase),
processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, cor, ordem) processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, cor, ordem)
`) `)
@@ -188,6 +188,10 @@ export default function Visualizador3D() {
apontamentosData.sort((a: any, b: any) => (a.processo?.ordem || 0) - (b.processo?.ordem || 0)); apontamentosData.sort((a: any, b: any) => (a.processo?.ordem || 0) - (b.processo?.ordem || 0));
apontamentosData.forEach((ap: any) => { apontamentosData.forEach((ap: any) => {
// Ignora apontamentos de componentes para não inflar as peças mestres
if (ap.tipo_apontamento && ap.tipo_apontamento !== 'peca') return;
if (ap.componente_id) return;
const marca = ap.peca?.marca ? String(ap.peca.marca).trim() : ''; const marca = ap.peca?.marca ? String(ap.peca.marca).trim() : '';
const fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : ''; const fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : '';
if (marca) { if (marca) {
@@ -90,10 +90,10 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
.from('processos_fabricacao') .from('processos_fabricacao')
.select('id, nome, ordem'), .select('id, nome, ordem'),
// 3. Apontamentos da OF // 3. Apontamentos da OF (apenas peças mestres)
supabase supabase
.from('apontamentos_producao') .from('apontamentos_producao')
.select('peca_id, processo_id, quantidade_produzida') .select('peca_id, processo_id, quantidade_produzida, tipo_apontamento, componente_id')
.eq('of_number', obra.of_number), .eq('of_number', obra.of_number),
// 4. Expedição em Romaneios (APENAS ROMANEIOS ENTREGUES E MAPEANDO POR PECA_ID PARA EVITAR CONFLITO DE FASE) // 4. Expedição em Romaneios (APENAS ROMANEIOS ENTREGUES E MAPEANDO POR PECA_ID PARA EVITAR CONFLITO DE FASE)
@@ -132,6 +132,10 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
>(); >();
(resApontamentos.data || []).forEach((ap: any) => { (resApontamentos.data || []).forEach((ap: any) => {
// Ignora apontamentos de componentes para não inflar as quantidades da peça mestre
if (ap.tipo_apontamento && ap.tipo_apontamento !== 'peca') return;
if (ap.componente_id) return;
const pecaId = ap.peca_id; const pecaId = ap.peca_id;
const nomeProc = mapaNomeProcesso.get(ap.processo_id) || ''; const nomeProc = mapaNomeProcesso.get(ap.processo_id) || '';
const qtd = Number(ap.quantidade_produzida || 0); const qtd = Number(ap.quantidade_produzida || 0);
@@ -94,7 +94,8 @@ export const calcularSaldoProcessoPeca = (
if (procAnteriorValido) { if (procAnteriorValido) {
const keyAnt = `${peca.id}_${procAnteriorValido.id}`; const keyAnt = `${peca.id}_${procAnteriorValido.id}`;
const qtdApontadaAnterior = mapaProd.get(keyAnt) || 0; const qtdApontadaAnterior = mapaProd.get(keyAnt) || 0;
qtdDisponivelParaEntrar = qtdApontadaAnterior; // Trava de teto: o saldo para entrar jamais pode exceder o total da peça
qtdDisponivelParaEntrar = Math.min(Number(peca.quantidade) || 0, qtdApontadaAnterior);
if (qtdApontadaAnterior === 0) { if (qtdApontadaAnterior === 0) {
motivoBloqueio = `Aguardando ${procAnteriorValido.nome}`; motivoBloqueio = `Aguardando ${procAnteriorValido.nome}`;
@@ -127,7 +128,7 @@ export const calcularSaldoProcessoPeca = (
} }
} }
const saldo = Math.max(0, qtdDisponivelParaEntrar - jaProduzidoAtual); const saldo = Math.max(0, Math.min(Number(peca.quantidade) || 0, qtdDisponivelParaEntrar) - jaProduzidoAtual);
return { return {
saldo, saldo,
@@ -149,6 +150,8 @@ interface ApontamentoItem {
status_confirmacao?: string; status_confirmacao?: string;
forcado_por_user_nome?: string; forcado_por_user_nome?: string;
usuario_nome?: string; usuario_nome?: string;
tipo_apontamento?: string;
componente_id?: string | null;
} }
export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
@@ -190,7 +193,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
.eq('of_number', obra.of_number), .eq('of_number', obra.of_number),
supabase supabase
.from('apontamentos_producao') .from('apontamentos_producao')
.select('id, peca_id, processo_id, quantidade_produzida, is_forcado, status_confirmacao, forcado_por_user_nome, usuario_nome') .select('id, peca_id, processo_id, quantidade_produzida, is_forcado, status_confirmacao, forcado_por_user_nome, usuario_nome, tipo_apontamento, componente_id')
.eq('of_number', obra.of_number), .eq('of_number', obra.of_number),
]); ]);
@@ -224,10 +227,14 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
return lista.length > 0 ? lista : ['Geral']; return lista.length > 0 ? lista : ['Geral'];
}, [pecas]); }, [pecas]);
// Mapa de produção acumulada por peca_id e processo_id // Mapa de produção acumulada por peca_id e processo_id (apenas apontamentos de peças mestres)
const mapaProducao = useMemo(() => { const mapaProducao = useMemo(() => {
const mapa = new Map<string, number>(); const mapa = new Map<string, number>();
apontamentosExistentes.forEach((ap) => { apontamentosExistentes.forEach((ap) => {
// Ignora apontamentos de componentes para não inflar a contagem da peça pai
if (ap.tipo_apontamento && ap.tipo_apontamento !== 'peca') return;
if (ap.componente_id) return;
const key = `${ap.peca_id}_${ap.processo_id}`; const key = `${ap.peca_id}_${ap.processo_id}`;
const atual = mapa.get(key) || 0; const atual = mapa.get(key) || 0;
mapa.set(key, atual + Number(ap.quantidade_produzida || 0)); mapa.set(key, atual + Number(ap.quantidade_produzida || 0));
@@ -238,7 +245,11 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
// Apontamentos forçados que ainda estão pendentes de confirmação de baixa // Apontamentos forçados que ainda estão pendentes de confirmação de baixa
const apontamentosPendentes = useMemo(() => { const apontamentosPendentes = useMemo(() => {
return apontamentosExistentes.filter( return apontamentosExistentes.filter(
(ap) => ap.is_forcado && ap.status_confirmacao === 'pendente_confirmacao' (ap) =>
(!ap.tipo_apontamento || ap.tipo_apontamento === 'peca') &&
!ap.componente_id &&
ap.is_forcado &&
ap.status_confirmacao === 'pendente_confirmacao'
); );
}, [apontamentosExistentes]); }, [apontamentosExistentes]);
@@ -69,7 +69,7 @@ export const SmartViewer3DFlow: React.FC<SmartViewer3DFlowProps> = ({
supabase supabase
.from('apontamentos_producao' as any) .from('apontamentos_producao' as any)
.select(` .select(`
id, of_number, peca_id, quantidade_produzida, id, of_number, peca_id, quantidade_produzida, tipo_apontamento, componente_id,
peca:pecas!apontamentos_producao_peca_id_fkey(marca, etapa_fase), peca:pecas!apontamentos_producao_peca_id_fkey(marca, etapa_fase),
processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, cor, ordem) processo:processos_fabricacao!apontamentos_producao_processo_id_fkey(nome, cor, ordem)
`) `)
@@ -139,6 +139,10 @@ export const SmartViewer3DFlow: React.FC<SmartViewer3DFlowProps> = ({
); );
sortedApontamentos.forEach((ap: any) => { sortedApontamentos.forEach((ap: any) => {
// Ignora apontamentos de componentes para não inflar as peças mestres
if (ap.tipo_apontamento && ap.tipo_apontamento !== 'peca') return;
if (ap.componente_id) return;
const marca = ap.peca?.marca ? String(ap.peca.marca).trim() : ''; const marca = ap.peca?.marca ? String(ap.peca.marca).trim() : '';
const fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : ''; const fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : '';
if (marca) { if (marca) {