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')
.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
.from('apontamentos_producao' as any)
.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),
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.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 fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : '';
if (marca) {
@@ -90,10 +90,10 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
.from('processos_fabricacao')
.select('id, nome, ordem'),
// 3. Apontamentos da OF
// 3. Apontamentos da OF (apenas peças mestres)
supabase
.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),
// 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) => {
// 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 nomeProc = mapaNomeProcesso.get(ap.processo_id) || '';
const qtd = Number(ap.quantidade_produzida || 0);
@@ -94,7 +94,8 @@ export const calcularSaldoProcessoPeca = (
if (procAnteriorValido) {
const keyAnt = `${peca.id}_${procAnteriorValido.id}`;
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) {
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 {
saldo,
@@ -149,6 +150,8 @@ interface ApontamentoItem {
status_confirmacao?: string;
forcado_por_user_nome?: string;
usuario_nome?: string;
tipo_apontamento?: string;
componente_id?: string | null;
}
export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
@@ -190,7 +193,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
.eq('of_number', obra.of_number),
supabase
.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),
]);
@@ -224,10 +227,14 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
return lista.length > 0 ? lista : ['Geral'];
}, [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 mapa = new Map<string, number>();
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 atual = mapa.get(key) || 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
const apontamentosPendentes = useMemo(() => {
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]);
@@ -69,7 +69,7 @@ export const SmartViewer3DFlow: React.FC<SmartViewer3DFlowProps> = ({
supabase
.from('apontamentos_producao' as any)
.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),
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) => {
// 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 fase = ap.peca?.etapa_fase ? String(ap.peca.etapa_fase).trim() : '';
if (marca) {