fix(expedicao): isolar peca principal no seletor de romaneio sem somar componentes
This commit is contained in:
@@ -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<ItensRomaneioModalProps> = ({
|
||||
.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<ItensRomaneioModalProps> = ({
|
||||
// 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<string, PecaDisponivel>();
|
||||
|
||||
// 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<string, { cortado: number; porPeca: number }>;
|
||||
}
|
||||
>();
|
||||
|
||||
// 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<ItensRomaneioModalProps> = ({
|
||||
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<ItensRomaneioModalProps> = ({
|
||||
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<ItensRomaneioModalProps> = ({
|
||||
<Label className={`font-medium mb-2 block ${isMobile ? 'text-sm' : 'text-sm'}`}>
|
||||
Peça Disponível
|
||||
</Label>
|
||||
<Select value={pecaDisponivel} onValueChange={setPecaDisponivel}>
|
||||
<Select
|
||||
value={pecaDisponivel}
|
||||
onValueChange={(val) => {
|
||||
setPecaDisponivel(val);
|
||||
const pSel = pecasDisponiveis.find((p) => p.id === val);
|
||||
if (pSel) {
|
||||
setQuantidadePeca(String(pSel.quantidade_disponivel));
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className={isMobile ? 'h-10' : 'h-11'}>
|
||||
<SelectValue placeholder="Selecionar peça" />
|
||||
</SelectTrigger>
|
||||
@@ -646,13 +716,30 @@ export const ItensRomaneioModal: React.FC<ItensRomaneioModalProps> = ({
|
||||
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 (
|
||||
<SelectItem key={peca.id} value={peca.id}>
|
||||
<div className="flex items-center justify-between gap-2 w-full">
|
||||
<span className="truncate">
|
||||
{peca.marca} - {peca.descricao} (Qtd: {peca.quantidade_disponivel})
|
||||
<span className="truncate font-medium">
|
||||
{peca.marca}
|
||||
{faseFormatada ? ` [${faseFormatada}]` : ''} - {descricaoCompleta} ({infoSaldo})
|
||||
</span>
|
||||
{peca.comprimento && (
|
||||
{peca.comprimento != null && Number(peca.comprimento) > 0 && (
|
||||
<span className={`text-[11px] font-mono shrink-0 ml-2 px-1.5 py-0.5 rounded ${
|
||||
naoCabe ? 'bg-destructive/20 text-destructive font-bold' : 'text-muted-foreground bg-muted'
|
||||
}`}>
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user