fix: permitir diminuir e zerar/anular quantidade apontada no modo smart
This commit is contained in:
@@ -400,12 +400,8 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
const hoje = new Date().toISOString().split('T')[0];
|
const hoje = new Date().toISOString().split('T')[0];
|
||||||
const nomeUsuario = user.name || user.username || user.email?.split('@')[0] || 'Operador';
|
const nomeUsuario = user.name || user.username || user.email?.split('@')[0] || 'Operador';
|
||||||
|
|
||||||
let delta = qtdApontar;
|
|
||||||
if (modoEdicao) {
|
if (modoEdicao) {
|
||||||
delta = qtdApontar - info.jaProduzido;
|
if (qtdApontar === info.jaProduzido) {
|
||||||
}
|
|
||||||
|
|
||||||
if (delta === 0) {
|
|
||||||
smartAudio.playClick();
|
smartAudio.playClick();
|
||||||
toast.info('Nenhuma alteração na quantidade foi feita.');
|
toast.info('Nenhuma alteração na quantidade foi feita.');
|
||||||
setEtapaAtual('pecas');
|
setEtapaAtual('pecas');
|
||||||
@@ -415,43 +411,82 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Em modo de edição/ajuste, removemos os apontamentos anteriores dessa peça/processo
|
||||||
|
// e, se a nova quantidade for > 0, inserimos o apontamento ajustado consolidado.
|
||||||
|
// Isso evita deltas negativos (que violam a check constraint quantidade_positiva)
|
||||||
|
// e permite zerar/anular o apontamento com perfeição.
|
||||||
|
const { error: deleteError } = await supabase
|
||||||
|
.from('apontamentos_producao')
|
||||||
|
.delete()
|
||||||
|
.eq('of_number', obra.of_number)
|
||||||
|
.eq('peca_id', pecaSelecionada.id)
|
||||||
|
.eq('processo_id', processoSelecionado.id);
|
||||||
|
|
||||||
|
if (deleteError) {
|
||||||
|
throw deleteError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (qtdApontar > 0) {
|
||||||
const payload = {
|
const payload = {
|
||||||
of_number: obra.of_number,
|
of_number: obra.of_number,
|
||||||
peca_id: pecaSelecionada.id,
|
peca_id: pecaSelecionada.id,
|
||||||
tipo_apontamento: 'peca' as const,
|
tipo_apontamento: 'peca' as const,
|
||||||
processo_id: processoSelecionado.id,
|
processo_id: processoSelecionado.id,
|
||||||
quantidade_produzida: delta,
|
quantidade_produzida: qtdApontar,
|
||||||
data_apontamento: hoje,
|
data_apontamento: hoje,
|
||||||
created_by: user.id,
|
created_by: user.id,
|
||||||
usuario_nome: nomeUsuario,
|
usuario_nome: nomeUsuario,
|
||||||
is_forcado: false,
|
is_forcado: false,
|
||||||
status_confirmacao: 'confirmado',
|
status_confirmacao: 'confirmado',
|
||||||
observacoes: modoEdicao ? 'Ajuste de quantidade via Modo Smart' : 'Apontado via Modo Smart',
|
observacoes: `Ajuste de quantidade total para ${qtdApontar} un via Modo Smart`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { error: insertError } = await supabase.from('apontamentos_producao').insert(payload);
|
||||||
|
if (insertError) throw insertError;
|
||||||
|
}
|
||||||
|
|
||||||
|
smartAudio.playSuccess();
|
||||||
|
if (qtdApontar === 0) {
|
||||||
|
toast.success(`🗑️ Apontamento anulado/zerado: ${pecaSelecionada.marca} (${processoSelecionado.nome})`);
|
||||||
|
} else {
|
||||||
|
toast.success(`✏️ Ajuste realizado: ${pecaSelecionada.marca} (${processoSelecionado.nome}) para ${qtdApontar} un`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = qtdApontar - info.jaProduzido;
|
||||||
|
const pesoTotalKg = delta * (pecaSelecionada.peso_unitario || 0);
|
||||||
|
onApontamentoRealizado(qtdApontar, pesoTotalKg);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Novo apontamento regular
|
||||||
|
const payload = {
|
||||||
|
of_number: obra.of_number,
|
||||||
|
peca_id: pecaSelecionada.id,
|
||||||
|
tipo_apontamento: 'peca' as const,
|
||||||
|
processo_id: processoSelecionado.id,
|
||||||
|
quantidade_produzida: qtdApontar,
|
||||||
|
data_apontamento: hoje,
|
||||||
|
created_by: user.id,
|
||||||
|
usuario_nome: nomeUsuario,
|
||||||
|
is_forcado: false,
|
||||||
|
status_confirmacao: 'confirmado',
|
||||||
|
observacoes: 'Apontado via Modo Smart',
|
||||||
};
|
};
|
||||||
|
|
||||||
const { error } = await supabase.from('apontamentos_producao').insert(payload);
|
const { error } = await supabase.from('apontamentos_producao').insert(payload);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BIP sonoro de sucesso e vibração háptica!
|
|
||||||
smartAudio.playSuccess();
|
smartAudio.playSuccess();
|
||||||
if (modoEdicao) {
|
|
||||||
toast.success(`✏️ Ajuste realizado: ${pecaSelecionada.marca} (${processoSelecionado.nome}) para ${qtdApontar} un`);
|
|
||||||
} else {
|
|
||||||
toast.success(`✅ Apontado: ${qtdApontar}x ${pecaSelecionada.marca} (${processoSelecionado.nome})`);
|
toast.success(`✅ Apontado: ${qtdApontar}x ${pecaSelecionada.marca} (${processoSelecionado.nome})`);
|
||||||
|
|
||||||
|
const pesoTotalKg = qtdApontar * (pecaSelecionada.peso_unitario || 0);
|
||||||
|
onApontamentoRealizado(qtdApontar, pesoTotalKg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notificar estatísticas do operador
|
// Atualiza os dados imediatamente e volta para a lista de peças daquela Fase/Processo
|
||||||
const pesoTotalKg = delta * (pecaSelecionada.peso_unitario || 0);
|
|
||||||
onApontamentoRealizado(qtdApontar, pesoTotalKg);
|
|
||||||
|
|
||||||
// FAST LOOP / STICKY CONTEXT:
|
|
||||||
// Atualiza os dados imediatamente e volta para a lista de peças daquela Fase/Processo!
|
|
||||||
await carregarDadosOF();
|
await carregarDadosOF();
|
||||||
|
|
||||||
// Permanece na mesma fase para apontar a próxima peça sem voltar tudo!
|
|
||||||
setEtapaAtual('pecas');
|
setEtapaAtual('pecas');
|
||||||
setPecaSelecionada(null);
|
setPecaSelecionada(null);
|
||||||
setModoEdicao(false);
|
setModoEdicao(false);
|
||||||
@@ -970,8 +1005,9 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
const somarQtd = (valor: number) => {
|
const somarQtd = (valor: number) => {
|
||||||
smartAudio.playClick();
|
smartAudio.playClick();
|
||||||
setQtdApontar((prev) => {
|
setQtdApontar((prev) => {
|
||||||
const nova = Math.max(1, prev + valor);
|
const minQtd = modoEdicao ? 0 : 1;
|
||||||
return saldoPendente > 0 ? Math.min(saldoPendente, nova) : nova;
|
const nova = Math.max(minQtd, prev + valor);
|
||||||
|
return maxPermitido > 0 ? Math.min(maxPermitido, nova) : nova;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1069,7 +1105,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => somarQtd(-1)}
|
onClick={() => somarQtd(-1)}
|
||||||
disabled={qtdApontar <= 0}
|
disabled={qtdApontar <= (modoEdicao ? 0 : 1)}
|
||||||
className="h-16 w-14 rounded-2xl bg-white hover:bg-slate-100 active:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 dark:active:bg-slate-700 border-2 border-slate-200 dark:border-slate-700 disabled:opacity-40 text-slate-800 dark:text-white flex items-center justify-center font-black active:scale-95 shadow-sm transition-colors"
|
className="h-16 w-14 rounded-2xl bg-white hover:bg-slate-100 active:bg-slate-200 dark:bg-slate-800 dark:hover:bg-slate-750 dark:active:bg-slate-700 border-2 border-slate-200 dark:border-slate-700 disabled:opacity-40 text-slate-800 dark:text-white flex items-center justify-center font-black active:scale-95 shadow-sm transition-colors"
|
||||||
title="Diminuir 1"
|
title="Diminuir 1"
|
||||||
>
|
>
|
||||||
@@ -1082,7 +1118,7 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
type="text"
|
type="text"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
pattern="[0-9]*"
|
pattern="[0-9]*"
|
||||||
value={qtdApontar === 0 ? '' : qtdApontar}
|
value={qtdApontar === 0 ? (modoEdicao ? '0' : '') : qtdApontar}
|
||||||
placeholder="0"
|
placeholder="0"
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
className="w-full text-center bg-transparent border-none outline-none text-3xl sm:text-4xl font-black text-amber-700 dark:text-amber-400 tracking-tight placeholder:text-slate-400 dark:placeholder:text-slate-700"
|
className="w-full text-center bg-transparent border-none outline-none text-3xl sm:text-4xl font-black text-amber-700 dark:text-amber-400 tracking-tight placeholder:text-slate-400 dark:placeholder:text-slate-700"
|
||||||
@@ -1149,9 +1185,9 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleLimpar}
|
onClick={handleLimpar}
|
||||||
className="h-13 rounded-xl bg-rose-100 hover:bg-rose-200 active:bg-rose-300 border border-rose-300 text-rose-800 dark:bg-red-950/40 dark:hover:bg-red-900/60 dark:active:bg-red-900 dark:border-red-800/50 dark:text-red-300 text-sm font-black active:scale-95 uppercase tracking-wider transition-colors"
|
className="h-13 rounded-xl bg-rose-100 hover:bg-rose-200 active:bg-rose-300 border border-rose-300 text-rose-800 dark:bg-red-950/40 dark:hover:bg-red-900/60 dark:active:bg-red-900 dark:border-red-800/50 dark:text-red-300 text-sm font-black active:scale-95 uppercase tracking-wider transition-colors"
|
||||||
title="Limpar valor"
|
title="Zerar / Limpar valor"
|
||||||
>
|
>
|
||||||
Limpar
|
Limpar (0)
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1174,9 +1210,13 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={salvando || qtdApontar <= 0}
|
disabled={salvando || (!modoEdicao && qtdApontar <= 0) || (modoEdicao && qtdApontar < 0)}
|
||||||
onClick={handleConfirmarApontamento}
|
onClick={handleConfirmarApontamento}
|
||||||
className="w-full h-15 bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white text-base font-black rounded-2xl shadow-lg shadow-emerald-950/20 dark:shadow-emerald-950/40 uppercase tracking-wide active:scale-98 gap-2"
|
className={`w-full h-15 text-base font-black rounded-2xl shadow-lg uppercase tracking-wide active:scale-98 gap-2 transition-all ${
|
||||||
|
modoEdicao && qtdApontar === 0
|
||||||
|
? 'bg-rose-600 hover:bg-rose-500 active:bg-rose-700 text-white shadow-rose-950/20 dark:shadow-rose-950/40'
|
||||||
|
: 'bg-emerald-600 hover:bg-emerald-500 active:bg-emerald-700 text-white shadow-emerald-950/20 dark:shadow-emerald-950/40'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{salvando ? (
|
{salvando ? (
|
||||||
<>
|
<>
|
||||||
@@ -1184,10 +1224,17 @@ export const SmartProducaoFlow: React.FC<SmartProducaoFlowProps> = ({
|
|||||||
<span>Gravando...</span>
|
<span>Gravando...</span>
|
||||||
</>
|
</>
|
||||||
) : modoEdicao ? (
|
) : modoEdicao ? (
|
||||||
|
qtdApontar === 0 ? (
|
||||||
|
<>
|
||||||
|
<Delete className="h-5 w-5" />
|
||||||
|
<span>ZERAR / ANULAR APONTAMENTO (0 UN)</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<Check className="h-5 w-5" />
|
<Check className="h-5 w-5" />
|
||||||
<span>SALVAR EDIÇÃO ({qtdApontar} UN)</span>
|
<span>SALVAR AJUSTE ({qtdApontar} UN)</span>
|
||||||
</>
|
</>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Check className="h-5 w-5" />
|
<Check className="h-5 w-5" />
|
||||||
|
|||||||
Reference in New Issue
Block a user