feat: precedencia sequencial de processos e regra pecas S/M (sem montagem)
This commit is contained in:
@@ -46,6 +46,7 @@ interface PecaStatus {
|
||||
pintura: number;
|
||||
expedido: number;
|
||||
montado_obra: number;
|
||||
tem_componentes?: boolean | null;
|
||||
}
|
||||
|
||||
export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowProps> = ({
|
||||
@@ -81,7 +82,7 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
|
||||
// 1. Peças da OF
|
||||
supabase
|
||||
.from('pecas')
|
||||
.select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, perfil_principal, material')
|
||||
.select('id, marca, descricao, etapa_fase, quantidade, peso_unitario, perfil_principal, material, tem_componentes')
|
||||
.eq('of_number', obra.of_number),
|
||||
|
||||
// 2. Processos de fabricação (para mapear nome sem depender de foreign key)
|
||||
@@ -196,6 +197,7 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
|
||||
pintura: procs.pintura,
|
||||
expedido: mapaExp.get(p.marca) || 0,
|
||||
montado_obra: mapaMont.get(p.marca) || 0,
|
||||
tem_componentes: p.tem_componentes,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -871,6 +873,12 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
|
||||
{peca.marca}
|
||||
</span>
|
||||
|
||||
{peca.tem_componentes === false && (
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50">
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
|
||||
{peca.perfil_principal && (
|
||||
<span className="text-xs px-2 py-0.5 rounded-md bg-amber-500/15 text-amber-300 font-black border border-amber-500/30">
|
||||
{peca.perfil_principal}
|
||||
@@ -905,14 +913,30 @@ export const SmartConsultaRelatoriosFlow: React.FC<SmartConsultaRelatoriosFlowPr
|
||||
{/* Linha do Tempo Visual com TODAS as Etapas Fabris */}
|
||||
<div className="grid grid-cols-7 gap-1 mt-3 pt-2.5 border-t border-slate-700/80 text-center">
|
||||
{[
|
||||
{ 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 (
|
||||
<div key={idx} className="flex flex-col items-center opacity-60">
|
||||
<div className="text-[9px] font-bold text-slate-400 mb-0.5 uppercase">
|
||||
{etapa.nome}
|
||||
</div>
|
||||
<div className="h-5 px-1 rounded-md bg-slate-900 border border-purple-800/40 mb-0.5 flex items-center justify-center">
|
||||
<span className="text-[9px] font-bold text-purple-400">S/M</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-bold text-slate-500">
|
||||
N/A
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const badge = getBadgeEtapa(etapa.valor, peca.quantidade);
|
||||
return (
|
||||
<div key={idx} className="flex flex-col items-center">
|
||||
|
||||
@@ -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<SmartEmbarqueFlowProps> = ({ 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<SmartEmbarqueFlowProps> = ({ obra, onVo
|
||||
{pecaEmbarque ? (
|
||||
<div className="p-4 rounded-2xl bg-slate-800 border-2 border-blue-500/80 mb-3 shadow-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-xl font-black text-amber-400">
|
||||
{pecaEmbarque.marca}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl font-black text-amber-400">
|
||||
{pecaEmbarque.marca}
|
||||
</span>
|
||||
{pecaEmbarque.tem_componentes === false && (
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50">
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs font-bold text-slate-400">
|
||||
Saldo Disponível: {pecaEmbarque.saldoDisponivel} un
|
||||
</span>
|
||||
@@ -1081,6 +1090,11 @@ export const SmartEmbarqueFlow: React.FC<SmartEmbarqueFlowProps> = ({ obra, onVo
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg font-black text-amber-400">{p.marca}</span>
|
||||
{p.tem_componentes === false && (
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50">
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[11px] px-2 py-0.5 rounded bg-slate-700 text-slate-300 font-semibold">
|
||||
{p.etapa_fase || 'Estrutural'}
|
||||
</span>
|
||||
@@ -1266,6 +1280,11 @@ export const SmartEmbarqueFlow: React.FC<SmartEmbarqueFlowProps> = ({ obra, onVo
|
||||
<span className="text-base font-black text-amber-400">
|
||||
{item.marca}
|
||||
</span>
|
||||
{(item.tem_componentes === false || pecasProntas.some(p => p.id === item.peca_id && p.tem_componentes === false)) && (
|
||||
<span className="text-[10px] px-2 py-0.5 rounded bg-purple-950/80 text-purple-300 font-bold border border-purple-800/40">
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
<span className="text-[10px] px-2 py-0.5 rounded bg-blue-950/80 text-blue-300 font-bold border border-blue-800/40">
|
||||
{item.fase || '1'}
|
||||
</span>
|
||||
|
||||
@@ -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<SmartMontagemFlowProps> = ({
|
||||
@@ -50,24 +51,41 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
|
||||
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<string, { marca: string; descricao: string; peso_unitario: number; expedido: number }>();
|
||||
(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<SmartMontagemFlowProps> = ({
|
||||
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<string, number>();
|
||||
(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<string, boolean>();
|
||||
(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<SmartMontagemFlowProps> = ({
|
||||
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<SmartMontagemFlowProps> = ({
|
||||
{/* Resumo da Peça */}
|
||||
<div className="p-4 rounded-2xl bg-slate-800/90 border border-slate-700 mb-4 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-2xl font-black text-amber-400">
|
||||
{itemSelecionado.marca}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl font-black text-amber-400">
|
||||
{itemSelecionado.marca}
|
||||
</span>
|
||||
{itemSelecionado.tem_componentes === false && (
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50">
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs font-black px-2.5 py-1 rounded-lg bg-emerald-500/20 text-emerald-300 border border-emerald-500/30 uppercase">
|
||||
Montagem em Obra
|
||||
</span>
|
||||
@@ -388,9 +407,16 @@ export const SmartMontagemFlow: React.FC<SmartMontagemFlowProps> = ({
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div>
|
||||
<span className="text-xl font-black text-amber-400 tracking-wider">
|
||||
{item.marca}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl font-black text-amber-400 tracking-wider">
|
||||
{item.marca}
|
||||
</span>
|
||||
{item.tem_componentes === false && (
|
||||
<span className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50">
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-slate-300 mt-0.5 line-clamp-1">
|
||||
{item.descricao || 'Peça Estrutural'}
|
||||
</div>
|
||||
|
||||
@@ -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<string, number>
|
||||
): 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<SmartProducaoFlowProps> = ({
|
||||
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<SmartProducaoFlowProps> = ({
|
||||
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<SmartProducaoFlowProps> = ({
|
||||
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<SmartProducaoFlowProps> = ({
|
||||
<span className="text-xl font-black text-amber-400 tracking-wider">
|
||||
{item.marca}
|
||||
</span>
|
||||
{item.tem_componentes === false && (
|
||||
<span
|
||||
className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50"
|
||||
title="Peça Sem Montagem (Pula Solda e Montagem)"
|
||||
>
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
{item.perfil_principal && (
|
||||
<span className="text-xs px-2 py-0.5 rounded-md bg-slate-700/80 text-slate-300 font-semibold">
|
||||
{item.perfil_principal}
|
||||
@@ -497,6 +574,12 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
||||
<div className="text-xs text-slate-300 mt-0.5 line-clamp-1">
|
||||
{item.descricao || 'Peça Estrutural'}
|
||||
</div>
|
||||
{item.motivoBloqueio && item.saldo === 0 && !concluida && (
|
||||
<div className="text-[11px] text-amber-400/90 font-medium mt-1 flex items-center gap-1">
|
||||
<AlertTriangle className="h-3.5 w-3.5 shrink-0 text-amber-400" />
|
||||
<span>{item.motivoBloqueio}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-right shrink-0">
|
||||
@@ -505,9 +588,13 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
||||
<span className="inline-flex items-center gap-1 text-xs font-black text-emerald-400 px-2 py-0.5 rounded-full bg-emerald-950/80 border border-emerald-700">
|
||||
<Check className="h-3 w-3" /> 100% Pronto
|
||||
</span>
|
||||
) : (
|
||||
) : item.saldo > 0 ? (
|
||||
<span className="inline-flex items-center gap-1 text-xs font-black text-amber-300 px-2 py-0.5 rounded-full bg-amber-950/80 border border-amber-700">
|
||||
Falta: {item.saldo}
|
||||
Disponível: {item.saldo}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1 text-[11px] font-bold text-slate-400 px-2 py-0.5 rounded-full bg-slate-800 border border-slate-700">
|
||||
Pendente ant.
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -529,18 +616,24 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
||||
<div className="mt-3 grid grid-cols-1 sm:grid-cols-3 gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
disabled={concluida}
|
||||
disabled={concluida || item.saldo <= 0}
|
||||
onClick={() => handleIniciarApontamentoPeca(item, item.saldo)}
|
||||
className={`sm:col-span-2 h-13 text-sm font-black rounded-xl uppercase tracking-wider transition-all active:scale-[0.98] ${
|
||||
concluida
|
||||
? 'bg-slate-800 text-slate-500 border border-slate-700'
|
||||
: item.saldo <= 0
|
||||
? 'bg-slate-800 text-slate-400 border border-slate-700 cursor-not-allowed'
|
||||
: 'bg-amber-500 hover:bg-amber-400 active:bg-amber-600 text-slate-950 shadow-md shadow-amber-950/30'
|
||||
}`}
|
||||
>
|
||||
{concluida ? 'Processo Concluído' : `DIGITAR / ESCOLHER QUANTIDADE`}
|
||||
{concluida
|
||||
? 'Processo Concluído'
|
||||
: item.saldo <= 0
|
||||
? item.motivoBloqueio || 'Indisponível neste processo'
|
||||
: `DIGITAR / ESCOLHER QUANTIDADE`}
|
||||
</Button>
|
||||
|
||||
{!concluida && (
|
||||
{!concluida && item.saldo > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
@@ -550,7 +643,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
||||
setEtapaAtual('quantidade');
|
||||
}}
|
||||
className="h-13 bg-slate-700 hover:bg-slate-650 active:bg-slate-600 text-amber-400 border border-slate-600 text-xs font-black rounded-xl uppercase active:scale-95"
|
||||
title="Apontar todo o saldo restante de uma vez"
|
||||
title="Apontar todo o saldo disponível de uma vez"
|
||||
>
|
||||
TODAS ({item.saldo})
|
||||
</Button>
|
||||
@@ -581,9 +674,13 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
||||
// 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<SmartProducaoFlowProps> = ({
|
||||
{/* Resumo da Peça no Topo */}
|
||||
<div className="p-3.5 rounded-2xl bg-slate-800/90 border border-slate-700 mb-3 shadow-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-xl font-black text-amber-400">
|
||||
{pecaSelecionada.marca}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl font-black text-amber-400">
|
||||
{pecaSelecionada.marca}
|
||||
</span>
|
||||
{pecaSelecionada.tem_componentes === false && (
|
||||
<span
|
||||
className="text-[10px] px-2 py-0.5 rounded-md bg-purple-950/80 text-purple-300 font-bold border border-purple-800/50"
|
||||
title="Peça Sem Montagem (Pula Solda e Montagem)"
|
||||
>
|
||||
S/M
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs font-black px-2.5 py-0.5 rounded-lg bg-amber-500/20 text-amber-300 border border-amber-500/30 uppercase">
|
||||
{processoSelecionado.nome}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user