fix: persistencia e extracao de comprimento de pecas e subcomponentes na importacao

This commit is contained in:
2026-10-16 15:43:15 -03:00
parent ec980cba05
commit ebcfe3c9d2
7 changed files with 141 additions and 17 deletions
@@ -26,7 +26,7 @@ import * as XLSX from 'xlsx';
import { useAppLabels } from '@/hooks/useAppLabels';
import { usePecas } from '@/hooks/usePecas';
import { supabase } from '@/integrations/supabase/client';
import { analisarConflitosImportacao, consolidarPecasEComponentes } from '@/utils/importDiffService';
import { analisarConflitosImportacao, consolidarPecasEComponentes, extrairComprimentoSeguro, extrairComprimentoComponenteSeguro } from '@/utils/importDiffService';
import { ResultadoAnaliseImportacao } from '@/types/importDiff';
import { ImportarPecasDiffModal } from '@/components/pecas/ImportarPecasDiffModal';
@@ -835,8 +835,12 @@ const AdvanceSteelConverterContent: React.FC = () => {
try {
const importacaoRows: any[] = [];
extractedData.forEach((item) => {
const comprPeca = extrairComprimentoSeguro(item.comprimentoMax);
if (item.components && item.components.length > 0) {
item.components.forEach((comp) => {
const comprComp = extrairComprimentoComponenteSeguro(comp.comprimento);
importacaoRows.push({
of_number: item.of,
etapa_fase: item.fase,
@@ -845,6 +849,8 @@ const AdvanceSteelConverterContent: React.FC = () => {
quantidade: Number(item.quantidade),
peso_unitario: item.pesoUnit === '-' ? 0 : Number(item.pesoUnit),
peso_total: item.pesoTotal === '-' ? 0 : Number(item.pesoTotal),
comprimento: comprPeca,
comprimento_ref: comprPeca,
tratamento_superficial: item.tratamentoSuperficial || tratamentoGlobal,
material: item.material,
perfil_principal: item.perfilPrincipal,
@@ -853,6 +859,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
descricao_componente: comp.perfil || item.descricao,
perfil_componente: comp.perfil || item.descricao,
peso_unitario_componente: comp.pesoUnitario || 0,
comprimento_componente: comprComp,
quantidade_por_peca: Number(comp.quantidade) || 1
});
});
@@ -865,6 +872,8 @@ const AdvanceSteelConverterContent: React.FC = () => {
quantidade: Number(item.quantidade),
peso_unitario: item.pesoUnit === '-' ? 0 : Number(item.pesoUnit),
peso_total: item.pesoTotal === '-' ? 0 : Number(item.pesoTotal),
comprimento: comprPeca,
comprimento_ref: comprPeca,
tratamento_superficial: item.tratamentoSuperficial || tratamentoGlobal,
material: item.material,
perfil_principal: item.perfilPrincipal,
+10 -1
View File
@@ -25,7 +25,7 @@ import {
import { toast } from 'sonner';
import * as XLSX from 'xlsx';
import { usePecas } from '@/hooks/usePecas';
import { analisarConflitosImportacao, consolidarPecasEComponentes } from '@/utils/importDiffService';
import { analisarConflitosImportacao, consolidarPecasEComponentes, extrairComprimentoSeguro, extrairComprimentoComponenteSeguro } from '@/utils/importDiffService';
import { ResultadoAnaliseImportacao } from '@/types/importDiff';
import { ImportarPecasDiffModal } from '@/components/pecas/ImportarPecasDiffModal';
@@ -532,8 +532,12 @@ export const BocadConverter: React.FC = () => {
try {
const importacaoRows: any[] = [];
extractedPieces.forEach((item) => {
const comprPeca = extrairComprimentoSeguro(item.comprimentoRef);
if (item.compostoPorComponentes === 'SIM' && item.components && item.components.length > 0) {
item.components.forEach((comp) => {
const comprComp = extrairComprimentoComponenteSeguro(comp.compr);
importacaoRows.push({
of_number: item.of,
etapa_fase: item.fase,
@@ -542,6 +546,8 @@ export const BocadConverter: React.FC = () => {
quantidade: Number(item.quantidade),
peso_unitario: item.pesoUnitario || 0,
peso_total: item.pesoTotal || 0,
comprimento: comprPeca,
comprimento_ref: comprPeca,
tratamento_superficial: item.tratamentoSuperficial,
material: item.material,
perfil_principal: item.perfilPrincipal,
@@ -550,6 +556,7 @@ export const BocadConverter: React.FC = () => {
descricao_componente: comp.perfil || item.descricao,
perfil_componente: comp.perfil || item.descricao,
peso_unitario_componente: comp.pesoUnit || 0,
comprimento_componente: comprComp,
quantidade_por_peca: Number(comp.quant) || 1
});
});
@@ -562,6 +569,8 @@ export const BocadConverter: React.FC = () => {
quantidade: Number(item.quantidade),
peso_unitario: item.pesoUnitario || 0,
peso_total: item.pesoTotal || 0,
comprimento: comprPeca,
comprimento_ref: comprPeca,
tratamento_superficial: item.tratamentoSuperficial,
material: item.material,
perfil_principal: item.perfilPrincipal,
+6 -1
View File
@@ -25,10 +25,13 @@ interface PecaCSVData {
material: string;
perfil_principal: string;
tem_componentes: boolean;
comprimento?: number | null;
comprimento_ref?: number | string;
marca_componente?: string;
descricao_componente?: string;
perfil_componente?: string;
peso_unitario_componente?: number;
comprimento_componente?: number | null;
quantidade_por_peca?: number;
}
@@ -108,7 +111,7 @@ export function ImportarPecasModal({ open, onOpenChange, onImport, pecasExistent
});
// Validar e converter campos numéricos
const numericFields = ['quantidade', 'peso_unitario', 'peso_total', 'peso_unitario_componente', 'quantidade_por_peca'];
const numericFields = ['quantidade', 'peso_unitario', 'peso_total', 'peso_unitario_componente', 'quantidade_por_peca', 'comprimento', 'comprimento_ref', 'comprimento_componente'];
const convertedData: any = { ...rawRow };
numericFields.forEach(field => {
@@ -177,10 +180,12 @@ export function ImportarPecasModal({ open, onOpenChange, onImport, pecasExistent
material: convertedData.material?.toString().trim() || '',
perfil_principal: convertedData.perfil_principal?.toString().trim() || '',
tem_componentes: convertedData.tem_componentes,
comprimento: convertedData.comprimento ? Number(convertedData.comprimento) : (convertedData.comprimento_ref ? Number(convertedData.comprimento_ref) : null),
marca_componente: convertedData.marca_componente?.toString().trim() || '',
descricao_componente: convertedData.descricao_componente?.toString().trim() || '',
perfil_componente: convertedData.perfil_componente?.toString().trim() || '',
peso_unitario_componente: convertedData.peso_unitario_componente || 0,
comprimento_componente: convertedData.comprimento_componente ? Number(convertedData.comprimento_componente) : null,
quantidade_por_peca: convertedData.quantidade_por_peca || 0
};
+4 -3
View File
@@ -15,7 +15,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
import { FileSpreadsheet, Upload, Download, CheckCircle2, AlertCircle, Trash2, ArrowLeft, RefreshCw } from 'lucide-react';
import { toast } from 'sonner';
import * as XLSX from 'xlsx';
import { analisarConflitosImportacao, consolidarPecasEComponentes } from '@/utils/importDiffService';
import { analisarConflitosImportacao, consolidarPecasEComponentes, extrairComprimentoSeguro, extrairComprimentoComponenteSeguro } from '@/utils/importDiffService';
import { ResultadoAnaliseImportacao } from '@/types/importDiff';
import { ImportarPecasDiffModal } from '@/components/pecas/ImportarPecasDiffModal';
@@ -471,8 +471,8 @@ export function ImportarXLSModal({
quantidade: p.quantidade,
peso_unitario: p.peso_unitario,
peso_total: p.peso_total,
comprimento: p.comprimento !== undefined && p.comprimento !== null ? Number(p.comprimento) : (p.comprimento_ref ? Number(p.comprimento_ref) : null),
comprimento_ref: p.comprimento_ref ? Number(p.comprimento_ref) : (p.comprimento ? Number(p.comprimento) : null),
comprimento: extrairComprimentoSeguro(p),
comprimento_ref: extrairComprimentoSeguro(p),
tratamento_superficial: p.tratamento_superficial || 'pintura',
material: p.material,
perfil_principal: p.perfil_principal,
@@ -481,6 +481,7 @@ export function ImportarXLSModal({
descricao_componente: p.descricao_componente,
perfil_componente: p.perfil_componente,
peso_unitario_componente: p.peso_unitario_componente,
comprimento_componente: extrairComprimentoComponenteSeguro(p),
quantidade_por_peca: p.quantidade_por_peca,
}));
+7 -2
View File
@@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { useAuth } from '@/hooks/useAuth';
import { extrairComprimentoSeguro, extrairComprimentoComponenteSeguro } from '@/utils/importDiffService';
export interface Peca {
id: string;
@@ -425,6 +426,7 @@ export function usePecas() {
pecasData.forEach((item, index) => {
const pecaKey = `${item.of_number || ''}-${item.etapa_fase || ''}-${item.marca || ''}`;
const comprimentoPeca = extrairComprimentoSeguro(item);
const pecaData = {
of_number: item.of_number || '',
@@ -434,7 +436,7 @@ export function usePecas() {
quantidade: Number(item.quantidade) || 0,
peso_unitario: Number(item.peso_unitario) || 0,
peso_total: Number(item.peso_total) || 0,
comprimento: item.comprimento ? Number(item.comprimento) : (item.comprimento_ref ? Number(item.comprimento_ref) : null),
comprimento: comprimentoPeca,
tratamento_superficial: item.tratamento_superficial || '',
material: item.material || '',
perfil_principal: item.perfil_principal || '',
@@ -445,6 +447,8 @@ export function usePecas() {
if (!pecasMap.has(pecaKey)) {
pecasMap.set(pecaKey, pecaData);
} else if (comprimentoPeca && !pecasMap.get(pecaKey).comprimento) {
pecasMap.get(pecaKey).comprimento = comprimentoPeca;
}
if (item.marca_componente && String(item.marca_componente).trim()) {
@@ -470,12 +474,13 @@ export function usePecas() {
}
cleanMarca = String(numVal);
const compComprimento = extrairComprimentoComponenteSeguro(item);
const componenteData = {
marca_componente: cleanMarca,
descricao: item.descricao_componente || item.descricao || '',
perfil: item.perfil_componente || item.perfil_principal || '',
peso_unitario: Number(item.peso_unitario_componente) || 0,
comprimento: item.comprimento_componente ? Number(item.comprimento_componente) : null,
comprimento: compComprimento,
quantidade_por_peca: Math.max(1, Math.round(Number(item.quantidade_por_peca) || 1)),
user_id: user.id
};
+98 -7
View File
@@ -27,6 +27,90 @@ const CAMPOS_MONITORADOS: CampoConfig[] = [
{ key: 'tem_componentes', label: 'Tem Componentes', isCritico: true },
];
/**
* Função utilitária resiliente para extrair o comprimento numérico de uma peça principal.
* Suporta múltiplos aliases de importação (Bocad, Advance Steel, Excel, CSV) e formatações de string.
*/
export function extrairComprimentoSeguro(item: any): number | null {
if (!item) return null;
const raw =
item.comprimento ??
item.comprimento_ref ??
item.comprimentoRef ??
item.comprimentoMax ??
item.comprimento_max ??
item.comprimento_peca ??
item['Comprimento'] ??
item['Comprimento (mm)'] ??
item['Comprimento Ref. (mm)'] ??
item['Comprimento Max. (mm)'] ??
item['Comp. (mm)'] ??
item['Comp (mm)'] ??
item.compr ??
item.comp;
if (raw === undefined || raw === null || raw === '' || raw === '-') return null;
if (typeof raw === 'number') {
return isNaN(raw) || raw <= 0 ? null : Math.round(raw);
}
// String cleanup: trata formatos como "3.241", "3,241", "6000 mm", "5850.5"
let str = String(raw).trim().replace(/mm/gi, '').trim();
if (str.includes('.') && str.includes(',')) {
if (str.indexOf('.') < str.indexOf(',')) {
str = str.replace(/\./g, '').replace(',', '.');
} else {
str = str.replace(/,/g, '');
}
} else if (str.includes(',')) {
str = str.replace(',', '.');
}
const num = parseFloat(str);
return isNaN(num) || num <= 0 ? null : Math.round(num);
}
/**
* Função utilitária resiliente para extrair o comprimento numérico de subcomponentes.
*/
export function extrairComprimentoComponenteSeguro(item: any): number | null {
if (!item) return null;
const raw =
item.comprimento_componente ??
item.comprimentoComponente ??
item.comprimento ??
item.comprimento_ref ??
item.compr ??
item.comp ??
item['Comprimento Componente'] ??
item['Comprimento (mm)'] ??
item['Comprimento'] ??
item['Comp. (mm)'];
if (raw === undefined || raw === null || raw === '' || raw === '-') return null;
if (typeof raw === 'number') {
return isNaN(raw) || raw <= 0 ? null : Math.round(raw);
}
let str = String(raw).trim().replace(/mm/gi, '').trim();
if (str.includes('.') && str.includes(',')) {
if (str.indexOf('.') < str.indexOf(',')) {
str = str.replace(/\./g, '').replace(',', '.');
} else {
str = str.replace(/,/g, '');
}
} else if (str.includes(',')) {
str = str.replace(',', '.');
}
const num = parseFloat(str);
return isNaN(num) || num <= 0 ? null : Math.round(num);
}
/**
* Normaliza e agrupa linhas de peças e subcomponentes
*/
@@ -45,6 +129,7 @@ export function consolidarPecasEComponentes(rawItems: any[]): {
if (!marca) return;
const pecaKey = `${ofNumber}-${etapaFase}-${marca}`;
const comprimentoFinal = extrairComprimentoSeguro(item);
const pecaData = {
of_number: ofNumber,
@@ -54,12 +139,7 @@ export function consolidarPecasEComponentes(rawItems: any[]): {
quantidade: Number(item.quantidade) || 0,
peso_unitario: Number(item.peso_unitario) || 0,
peso_total: Number(item.peso_total) || 0,
comprimento:
item.comprimento !== undefined && item.comprimento !== null
? Number(item.comprimento)
: item.comprimento_ref
? Number(item.comprimento_ref)
: null,
comprimento: comprimentoFinal,
tratamento_superficial: item.tratamento_superficial || 'pintura',
material: item.material || 'Aço A36',
perfil_principal: item.perfil_principal || item.descricao || '',
@@ -68,6 +148,9 @@ export function consolidarPecasEComponentes(rawItems: any[]): {
if (!pecasMap.has(pecaKey)) {
pecasMap.set(pecaKey, pecaData);
} else if (comprimentoFinal && !pecasMap.get(pecaKey).comprimento) {
// Se a peça já estava no mapa mas sem comprimento e esta linha tem, atualiza
pecasMap.get(pecaKey).comprimento = comprimentoFinal;
}
// Se o item contém um subcomponente
@@ -92,12 +175,14 @@ export function consolidarPecasEComponentes(rawItems: any[]): {
}
cleanMarca = String(numVal);
const compComprimento = extrairComprimentoComponenteSeguro(item);
const compData = {
marca_componente: cleanMarca,
descricao: item.descricao_componente || item.descricao || '',
perfil: item.perfil_componente || item.perfil_principal || '',
peso_unitario: Number(item.peso_unitario_componente) || 0,
comprimento: item.comprimento_componente ? Number(item.comprimento_componente) : null,
comprimento: compComprimento,
quantidade_por_peca: Math.max(1, Math.round(Number(item.quantidade_por_peca) || 1)),
};
@@ -110,6 +195,12 @@ export function consolidarPecasEComponentes(rawItems: any[]): {
const exists = comps.some((c) => c.marca_componente === cleanMarca);
if (!exists) {
comps.push(compData);
} else if (compComprimento) {
// Se já existia mas sem comprimento, atualiza
const cExistente = comps.find((c) => c.marca_componente === cleanMarca);
if (cExistente && !cExistente.comprimento) {
cExistente.comprimento = compComprimento;
}
}
}
});
+6 -2
View File
@@ -1,5 +1,6 @@
import { supabase } from '@/integrations/supabase/client';
import { ItemAnaliseImportacao } from '@/types/importDiff';
import { extrairComprimentoSeguro } from '@/utils/importDiffService';
export interface ExecucaoImportacaoParams {
itens: ItemAnaliseImportacao[];
@@ -75,12 +76,14 @@ export async function executarImportacaoConciliada(
const pesoUnitFinal = Number(importado.peso_unitario || existente.peso_unitario || 0);
const pesoTotalCalculado = Number((qtdFinal * pesoUnitFinal).toFixed(3)) || Number(importado.peso_total || 0);
const comprFinal = extrairComprimentoSeguro(importado) ?? extrairComprimentoSeguro(existente);
const payloadUpdate: any = {
descricao: importado.descricao || existente.descricao || '',
quantidade: qtdFinal,
peso_unitario: pesoUnitFinal,
peso_total: pesoTotalCalculado,
comprimento: importado.comprimento !== undefined ? importado.comprimento : existente.comprimento,
comprimento: comprFinal,
tratamento_superficial: importado.tratamento_superficial || existente.tratamento_superficial || 'pintura',
material: importado.material || existente.material || 'Aço A36',
perfil_principal: importado.perfil_principal || existente.perfil_principal || '',
@@ -170,6 +173,7 @@ export async function executarImportacaoConciliada(
const qtd = Number(imp.quantidade) || 0;
const pesoU = Number(imp.peso_unitario) || 0;
const pesoT = Number(imp.peso_total) || Number((qtd * pesoU).toFixed(3));
const comprFinal = extrairComprimentoSeguro(imp);
return {
of_number: imp.of_number,
@@ -179,7 +183,7 @@ export async function executarImportacaoConciliada(
quantidade: qtd,
peso_unitario: pesoU,
peso_total: pesoT,
comprimento: imp.comprimento || null,
comprimento: comprFinal,
tratamento_superficial: imp.tratamento_superficial || 'pintura',
material: imp.material || 'Aço A36',
perfil_principal: imp.perfil_principal || '',