fix: correcao no motor de conversao Advance Steel para extracao de quantidade em pecas simples e compostas
This commit is contained in:
+257
-152
@@ -386,190 +386,267 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processPdfLines(items) {
|
function processPdfLines(items) {
|
||||||
items.sort((a, b) => {
|
// 1. Agrupamento robusto de linhas por Clustering de Coordenada Y em cada página
|
||||||
if (a.page !== b.page) return a.page - b.page;
|
const pagesMap = new Map();
|
||||||
if (Math.abs(b.y - a.y) > 3) return b.y - a.y;
|
items.forEach((it) => {
|
||||||
return a.x - b.x;
|
if (!pagesMap.has(it.page)) pagesMap.set(it.page, []);
|
||||||
|
pagesMap.get(it.page).push(it);
|
||||||
});
|
});
|
||||||
|
|
||||||
const lines = [];
|
const lines = [];
|
||||||
let currentLine = [];
|
const Y_TOLERANCE = 6.0;
|
||||||
let lastY = null;
|
|
||||||
let lastPage = null;
|
|
||||||
|
|
||||||
items.forEach(item => {
|
const pageNumbers = Array.from(pagesMap.keys()).sort((a, b) => a - b);
|
||||||
if (lastPage === null || item.page !== lastPage || Math.abs(item.y - lastY) > 3.5) {
|
for (const pNum of pageNumbers) {
|
||||||
if (currentLine.length > 0) {
|
const pageItems = pagesMap.get(pNum);
|
||||||
lines.push(currentLine);
|
const clusters = [];
|
||||||
|
|
||||||
|
pageItems.sort((a, b) => b.y - a.y);
|
||||||
|
|
||||||
|
for (const item of pageItems) {
|
||||||
|
let bestCluster = null;
|
||||||
|
let minDiff = Infinity;
|
||||||
|
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
const diff = Math.abs(item.y - cluster.avgY);
|
||||||
|
if (diff <= Y_TOLERANCE && diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
bestCluster = cluster;
|
||||||
}
|
}
|
||||||
currentLine = [item];
|
}
|
||||||
lastY = item.y;
|
|
||||||
lastPage = item.page;
|
if (bestCluster) {
|
||||||
|
bestCluster.items.push(item);
|
||||||
|
bestCluster.ySum += item.y;
|
||||||
|
bestCluster.count += 1;
|
||||||
|
bestCluster.avgY = bestCluster.ySum / bestCluster.count;
|
||||||
} else {
|
} else {
|
||||||
currentLine.push(item);
|
clusters.push({
|
||||||
}
|
ySum: item.y,
|
||||||
|
count: 1,
|
||||||
|
avgY: item.y,
|
||||||
|
items: [item]
|
||||||
});
|
});
|
||||||
if (currentLine.length > 0) lines.push(currentLine);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters.sort((a, b) => b.avgY - a.avgY);
|
||||||
|
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
cluster.items.sort((a, b) => a.x - b.x);
|
||||||
|
lines.push(cluster.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log(`Total de linhas identificadas: ${lines.length}`);
|
log(`Total de linhas identificadas: ${lines.length}`);
|
||||||
|
|
||||||
let defaultOf = "";
|
let defaultOf = "";
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const lineText = line.map(i => i.text).join(' ');
|
const lineText = line.map(i => i.text).join(' ');
|
||||||
const matchTrabalho = lineText.match(/Trabalho:\s*([A-Za-z0-9\-]+)/i);
|
const matchTrabalho = lineText.match(/Trabalho:\s*([A-Za-z0-9\-]+)/i) || lineText.match(/Projeto:\s*([A-Za-z0-9\-]+)/i);
|
||||||
if (matchTrabalho) {
|
if (matchTrabalho) {
|
||||||
defaultOf = matchTrabalho[1].replace('-', '');
|
defaultOf = matchTrabalho[1].replace(/-/g, '');
|
||||||
log(`OF / Trabalho identificado no cabeçalho: ${defaultOf}`);
|
log(`OF / Trabalho identificado no cabeçalho: ${defaultOf}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KNOWN_MATERIALS = [
|
||||||
|
'A572-GR 50', 'A572-GR50', 'A572-GR.50', 'A572 GR50', 'A572GR50', 'A572GR5', 'A572',
|
||||||
|
'A36', 'ASTM A36', 'ASTM A572', '10.9', '8.8', 'SAE 1020', 'SAE 1045', 'INOX', '304', '316'
|
||||||
|
];
|
||||||
|
|
||||||
|
function normalizeLineText(lineStr) {
|
||||||
|
let s = lineStr.trim();
|
||||||
|
s = s.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)\s*-\s*(\d+)/g, '$1-$2-$3');
|
||||||
|
s = s.replace(/([A-Za-z0-9]+)\s*-\s*(\d+)/g, '$1-$2');
|
||||||
|
s = s.replace(/A572\s*-\s*GR\s*50/gi, 'A572-GR 50');
|
||||||
|
s = s.replace(/A572\s*-\s*GR/gi, 'A572-GR');
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
const assemblies = [];
|
const assemblies = [];
|
||||||
let currentAssembly = null;
|
let currentAssembly = null;
|
||||||
|
|
||||||
// Filtro estrito para evitar linhas duplicadas/ruídos de componentes repetidos na listagem
|
|
||||||
const processedMarks = new Set();
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
const lineText = line.map(i => i.text).join(' ');
|
const rawLineText = line.map(it => it.text).join(' ').trim();
|
||||||
|
const lineText = normalizeLineText(rawLineText);
|
||||||
|
|
||||||
if (lineText.includes('Lista de Peças') || lineText.includes('Cliente:') || lineText.includes('Marca Quant Nome') || lineText.includes('Superfície')) {
|
if (
|
||||||
|
lineText.includes('LISTA DE PEÇAS') ||
|
||||||
|
lineText.includes('LISTA ESTRUTURADA') ||
|
||||||
|
lineText.includes('Lista de Peças') ||
|
||||||
|
lineText.includes('DetailDwgExtract') ||
|
||||||
|
lineText.includes('Cliente:') ||
|
||||||
|
lineText.includes('Desenhado') ||
|
||||||
|
lineText.includes('Superfície') ||
|
||||||
|
lineText.includes('Trabalho:') ||
|
||||||
|
lineText.includes('Kg/piece') ||
|
||||||
|
lineText.includes('( mm)') ||
|
||||||
|
lineText.toLowerCase().includes('peso total:') ||
|
||||||
|
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
|
||||||
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstToken = line[0].text;
|
// 1. Linha Mestre de Peça Principal (ex: "B134-13-1 90 W 150x13.0" ou "B134-13-2 72 W 150x13.0")
|
||||||
const markMatch = firstToken.match(/^([A-Za-z0-9]+)-(\d+)-(\d+)$/) || firstToken.match(/^([A-Za-z0-9]+)-(\d+)$/);
|
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)(?:\s+(\d+))?\s+([A-Za-z].*)$/);
|
||||||
|
if (masterMatch) {
|
||||||
if (markMatch) {
|
const fullMark = masterMatch[1];
|
||||||
const parts = firstToken.split('-');
|
const quantFromLine = masterMatch[2] ? parseInt(masterMatch[2], 10) : 1;
|
||||||
let ofCode = "";
|
const descCandidate = masterMatch[3].trim();
|
||||||
let phaseCode = "0";
|
const parts = fullMark.split('-');
|
||||||
let pieceNumber = "";
|
const pieceNumber = parts[parts.length - 1];
|
||||||
|
|
||||||
if (parts.length === 3) {
|
|
||||||
ofCode = parts[0];
|
|
||||||
phaseCode = parts[1];
|
|
||||||
pieceNumber = parts[2];
|
|
||||||
} else if (parts.length === 2) {
|
|
||||||
ofCode = parts[0];
|
|
||||||
phaseCode = "0";
|
|
||||||
pieceNumber = parts[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
const pieceNumInt = parseInt(pieceNumber, 10);
|
const pieceNumInt = parseInt(pieceNumber, 10);
|
||||||
const isComponent = !isNaN(pieceNumInt) && pieceNumInt >= 1000;
|
|
||||||
|
|
||||||
if (!isComponent) {
|
if (pieceNumInt < 1000) {
|
||||||
// Evitar duplicidade caso a mesma marca principal apareça mais de uma vez agrupada
|
const hasMaterial = KNOWN_MATERIALS.some(m => descCandidate.includes(m));
|
||||||
if (processedMarks.has(firstToken)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
processedMarks.add(firstToken);
|
|
||||||
|
|
||||||
let mainQuant = 1;
|
if (!hasMaterial) {
|
||||||
let mainDesc = "";
|
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
|
||||||
|
const phaseCode = parts.length >= 3 ? parts[1] : (parts.length === 2 ? parts[0] : '1');
|
||||||
if (line.length >= 2) {
|
|
||||||
const qVal = parseInt(line[1].text, 10);
|
|
||||||
if (!isNaN(qVal)) {
|
|
||||||
mainQuant = qVal;
|
|
||||||
// CORREÇÃO 2: Garantir que a descrição seja única, pegando apenas o primeiro token descritivo limpo (ex: W 310x38.7)
|
|
||||||
let rawDescTokens = line.slice(2).map(item => item.text);
|
|
||||||
if (rawDescTokens.length > 0) {
|
|
||||||
mainDesc = rawDescTokens[0]; // Nome único/perfil inicial da peça principal
|
|
||||||
// Se o perfil tiver complemento separado por espaço (ex: W 310x38.7), agrupar se necessário ou manter limpo
|
|
||||||
if (rawDescTokens.length > 1 && (rawDescTokens[1].includes('x') || rawDescTokens[1].includes('Pl') || !isNaN(parseFloat(rawDescTokens[1])))) {
|
|
||||||
mainDesc = rawDescTokens.slice(0, 2).join(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let rawDescTokens = line.slice(1).map(item => item.text);
|
|
||||||
mainDesc = rawDescTokens.length > 0 ? rawDescTokens[0] : 'ESTRUTURA';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentAssembly = {
|
currentAssembly = {
|
||||||
rawMark: firstToken,
|
rawMark: fullMark,
|
||||||
of: ofCode || defaultOf,
|
of: ofCode || defaultOf,
|
||||||
fase: phaseCode,
|
fase: phaseCode || '1',
|
||||||
marca: pieceNumber, // CORREÇÃO 1: Apenas o número da peça (última numeração)
|
marca: pieceNumber,
|
||||||
numeroPeca: pieceNumber,
|
numeroPeca: pieceNumber,
|
||||||
descricao: mainDesc || 'ESTRUTURA',
|
descricao: descCandidate || 'ESTRUTURA',
|
||||||
quantidade: mainQuant,
|
quantidade: quantFromLine > 0 ? quantFromLine : 1,
|
||||||
|
material: '',
|
||||||
|
comprimento: 0,
|
||||||
pesoUnitario: 0,
|
pesoUnitario: 0,
|
||||||
pesoTotal: 0,
|
pesoTotal: 0,
|
||||||
|
subtotalPeso: 0,
|
||||||
|
isComposed: 'NÃO',
|
||||||
components: []
|
components: []
|
||||||
};
|
};
|
||||||
assemblies.push(currentAssembly);
|
assemblies.push(currentAssembly);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Linha com Subtotal da Marca
|
||||||
|
const tokens = lineText.split(/\s+/);
|
||||||
|
const allTokensNumeric = tokens.length > 0 && tokens.every(tok => /^[0-9.,]+$/.test(tok));
|
||||||
|
if (allTokensNumeric && tokens.length <= 3) {
|
||||||
|
if (currentAssembly && (!currentAssembly.subtotalPeso || currentAssembly.subtotalPeso === 0)) {
|
||||||
|
const subP = parseNumeric(tokens[0]);
|
||||||
|
if (subP > 0) {
|
||||||
|
currentAssembly.subtotalPeso = subP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Linha de Detalhe ou Componente
|
||||||
|
let itemMark = '';
|
||||||
|
let itemQuant = 1;
|
||||||
|
let remaining = lineText;
|
||||||
|
|
||||||
|
const mMark = remaining.match(/^([A-Za-z0-9]+(?:-\d+)+|-)\s+(\d+)\s+/);
|
||||||
|
if (mMark) {
|
||||||
|
itemMark = mMark[1];
|
||||||
|
itemQuant = parseInt(mMark[2], 10);
|
||||||
|
remaining = remaining.substring(mMark[0].length).trim();
|
||||||
|
} else if (remaining.startsWith('-')) {
|
||||||
|
itemMark = '-';
|
||||||
|
remaining = remaining.substring(1).trim();
|
||||||
|
const mQ = remaining.match(/^(\d+)\s+/);
|
||||||
|
if (mQ) {
|
||||||
|
itemQuant = parseInt(mQ[1], 10);
|
||||||
|
remaining = remaining.substring(mQ[0].length).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let detectedMaterial = '';
|
||||||
|
let matIdx = -1;
|
||||||
|
let matLen = 0;
|
||||||
|
|
||||||
|
for (const mat of KNOWN_MATERIALS) {
|
||||||
|
const idx = remaining.indexOf(mat);
|
||||||
|
if (idx !== -1 && idx > matIdx) {
|
||||||
|
detectedMaterial = mat;
|
||||||
|
matIdx = idx;
|
||||||
|
matLen = mat.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemDesc = '';
|
||||||
|
let afterMat = '';
|
||||||
|
|
||||||
|
if (matIdx !== -1) {
|
||||||
|
itemDesc = remaining.substring(0, matIdx).trim();
|
||||||
|
afterMat = remaining.substring(matIdx + matLen).trim();
|
||||||
} else {
|
} else {
|
||||||
// Componente subordinado
|
const parts = remaining.split(/\s+/);
|
||||||
|
const firstNumIdx = parts.findIndex(p => /^[0-9.,]+$/.test(p));
|
||||||
|
if (firstNumIdx > 0) {
|
||||||
|
itemDesc = parts.slice(0, firstNumIdx).join(' ');
|
||||||
|
afterMat = parts.slice(firstNumIdx).join(' ');
|
||||||
|
} else {
|
||||||
|
itemDesc = remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const numTokens = afterMat.split(/\s+/).filter(tok => /^[0-9.,]+$/.test(tok));
|
||||||
|
const nums = numTokens.map(parseNumeric);
|
||||||
|
|
||||||
|
let itemComp = 0;
|
||||||
|
let itemPUn = 0;
|
||||||
|
let itemPesoTot = 0;
|
||||||
|
|
||||||
|
if (nums.length >= 1) {
|
||||||
|
itemComp = nums[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nums.length === 3) {
|
||||||
|
itemPUn = nums[1];
|
||||||
|
itemPesoTot = nums[2];
|
||||||
|
} else if (nums.length === 4) {
|
||||||
|
itemPUn = nums[1];
|
||||||
|
itemPesoTot = nums[2];
|
||||||
|
} else if (nums.length === 5) {
|
||||||
|
itemPUn = nums[1];
|
||||||
|
itemPesoTot = nums[2];
|
||||||
|
} else if (nums.length >= 6) {
|
||||||
|
itemPUn = nums[2];
|
||||||
|
itemPesoTot = nums[3];
|
||||||
|
}
|
||||||
|
|
||||||
if (currentAssembly) {
|
if (currentAssembly) {
|
||||||
let compQuant = 1;
|
const isSubComp =
|
||||||
let compProfile = "";
|
itemMark === '-' ||
|
||||||
let compMaterial = "A36";
|
itemMark.includes('-100') ||
|
||||||
let compLength = 0;
|
itemMark.includes('-10') ||
|
||||||
let compWeightTotal = 0;
|
(itemMark.match(/-\d+$/) && parseInt(itemMark.match(/-(\d+)$/)[1], 10) >= 1000);
|
||||||
|
|
||||||
let compTokens = line.map(it => it.text);
|
|
||||||
if (compTokens.length >= 2) {
|
|
||||||
const qTest = parseInt(compTokens[1], 10);
|
|
||||||
if (!isNaN(qTest)) compQuant = qTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
let matIndex = -1;
|
|
||||||
for (let k = 1; k < compTokens.length; k++) {
|
|
||||||
const tok = compTokens[k].toUpperCase();
|
|
||||||
if (tok.includes('A572') || tok.includes('A36') || tok.includes('A500') || tok.includes('A106') || tok.includes('GR') || tok.includes('INOX') || tok.includes('SAE')) {
|
|
||||||
matIndex = k;
|
|
||||||
compMaterial = compTokens[k];
|
|
||||||
if (k + 1 < compTokens.length && (compTokens[k+1].toUpperCase().includes('GR') || compTokens[k+1] === '50')) {
|
|
||||||
compMaterial += ' ' + compTokens[k+1];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matIndex > 2) {
|
|
||||||
compProfile = compTokens.slice(2, matIndex).join(' ');
|
|
||||||
} else if (compTokens.length >= 3) {
|
|
||||||
compProfile = compTokens[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
const afterMatTokens = matIndex !== -1 ? compTokens.slice(matIndex + 1) : compTokens.slice(3);
|
|
||||||
const numericValues = [];
|
|
||||||
afterMatTokens.forEach(t => {
|
|
||||||
const cleanT = t.replace(/[()]/g, '');
|
|
||||||
if (/[0-9]/.test(cleanT) && !cleanT.toUpperCase().includes('GR')) {
|
|
||||||
numericValues.push(cleanT);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (numericValues.length >= 1) {
|
|
||||||
compLength = parseNumeric(numericValues[0]);
|
|
||||||
}
|
|
||||||
// O peso total do componente geralmente é o último ou penúltimo valor numérico da linha de detalhe
|
|
||||||
if (numericValues.length >= 3) {
|
|
||||||
compWeightTotal = parseNumeric(numericValues[numericValues.length - 1]);
|
|
||||||
} else if (numericValues.length === 2) {
|
|
||||||
compWeightTotal = parseNumeric(numericValues[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (isSubComp) {
|
||||||
|
currentAssembly.isComposed = 'SIM';
|
||||||
currentAssembly.components.push({
|
currentAssembly.components.push({
|
||||||
quantidade: compQuant,
|
quantidade: itemQuant,
|
||||||
perfil: compProfile,
|
perfil: itemDesc || currentAssembly.descricao,
|
||||||
material: compMaterial,
|
material: detectedMaterial || 'A36',
|
||||||
comprimento: compLength,
|
comprimento: itemComp,
|
||||||
pesoTotal: compWeightTotal
|
pesoUnitario: itemPUn,
|
||||||
|
pesoTotal: itemPesoTot
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Linha de subtotal ou peso geral da peça principal
|
// Monopeça
|
||||||
if (currentAssembly && line.length <= 3) {
|
if (!currentAssembly.material && detectedMaterial) {
|
||||||
const possibleTotal = parseNumeric(line[0].text);
|
currentAssembly.material = detectedMaterial;
|
||||||
if (possibleTotal > 10 && currentAssembly.pesoTotal === 0) {
|
}
|
||||||
currentAssembly.pesoTotal = possibleTotal;
|
if (itemComp > 0 && (!currentAssembly.comprimento || currentAssembly.comprimento === 0)) {
|
||||||
|
currentAssembly.comprimento = itemComp;
|
||||||
|
}
|
||||||
|
if (itemPUn > 0 && (!currentAssembly.pesoUnitario || currentAssembly.pesoUnitario === 0)) {
|
||||||
|
currentAssembly.pesoUnitario = itemPUn;
|
||||||
|
}
|
||||||
|
if (itemPesoTot > 0 && (!currentAssembly.pesoTotal || currentAssembly.pesoTotal === 0)) {
|
||||||
|
currentAssembly.pesoTotal = itemPesoTot;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -578,45 +655,73 @@
|
|||||||
log(`Total de Peças Principais processadas: ${assemblies.length}`);
|
log(`Total de Peças Principais processadas: ${assemblies.length}`);
|
||||||
|
|
||||||
extractedData = assemblies.map(asm => {
|
extractedData = assemblies.map(asm => {
|
||||||
const hasComponents = asm.components.length > 0;
|
const isComposed = asm.isComposed === 'SIM' && asm.components.length > 0;
|
||||||
|
|
||||||
let perfilPrincipal = asm.descricao;
|
let perfilPrincipal = asm.descricao;
|
||||||
let materialPrincipal = "A36";
|
let materialPrincipal = 'A36';
|
||||||
let maxComp = 0;
|
let maxComp = asm.comprimento || 0;
|
||||||
let sumComponentsWeight = 0;
|
let totalPeso = asm.subtotalPeso > 0 ? asm.subtotalPeso : asm.pesoTotal;
|
||||||
|
|
||||||
if (hasComponents) {
|
if (isComposed) {
|
||||||
|
let sumComponentsWeight = 0;
|
||||||
|
let sumUnitComponentsWeight = 0;
|
||||||
let bestComp = asm.components[0];
|
let bestComp = asm.components[0];
|
||||||
|
maxComp = 0;
|
||||||
|
|
||||||
asm.components.forEach(c => {
|
asm.components.forEach(c => {
|
||||||
if (c.comprimento > maxComp) {
|
if (c.comprimento > maxComp) {
|
||||||
maxComp = c.comprimento;
|
maxComp = c.comprimento;
|
||||||
bestComp = c;
|
bestComp = c;
|
||||||
}
|
}
|
||||||
sumComponentsWeight += c.pesoTotal;
|
sumComponentsWeight += c.pesoTotal;
|
||||||
|
if (c.pesoUnitario && c.pesoUnitario > 0) {
|
||||||
|
sumUnitComponentsWeight += c.pesoUnitario * c.quantidade;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (bestComp) {
|
||||||
perfilPrincipal = bestComp.perfil || asm.descricao;
|
perfilPrincipal = bestComp.perfil || asm.descricao;
|
||||||
materialPrincipal = bestComp.material || "A36";
|
materialPrincipal = bestComp.material || 'A36';
|
||||||
|
maxComp = bestComp.comprimento;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Definir peso total final (prioriza o subtotal extraído do relatório)
|
if (!totalPeso || totalPeso === 0) {
|
||||||
let finalPesoTotal = asm.pesoTotal > 0 ? asm.pesoTotal : sumComponentsWeight;
|
totalPeso = sumComponentsWeight;
|
||||||
|
}
|
||||||
|
|
||||||
// CORREÇÃO 3: Peso Unitário = Peso Total dividido pela quantidade da peça principal
|
if (asm.quantidade === 1 && sumUnitComponentsWeight > 0 && totalPeso > 0) {
|
||||||
let finalPesoUnit = asm.quantidade > 0 ? (finalPesoTotal / asm.quantidade) : finalPesoTotal;
|
const ratio = Math.round(totalPeso / sumUnitComponentsWeight);
|
||||||
|
if (ratio > 1) {
|
||||||
|
asm.quantidade = ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Monopeça simples
|
||||||
|
if (!totalPeso || totalPeso === 0) {
|
||||||
|
totalPeso = (asm.pesoUnitario || 0) * asm.quantidade;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asm.quantidade === 1 && asm.pesoUnitario > 0 && totalPeso > asm.pesoUnitario) {
|
||||||
|
const ratio = Math.round(totalPeso / asm.pesoUnitario);
|
||||||
|
if (ratio > 1) {
|
||||||
|
asm.quantidade = ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pesoUnit = asm.quantidade > 0 ? (totalPeso / asm.quantidade) : (asm.pesoUnitario || totalPeso);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
of: asm.of,
|
of: asm.of,
|
||||||
fase: asm.fase,
|
fase: asm.fase,
|
||||||
marca: asm.marca,
|
marca: asm.marca,
|
||||||
descricao: asm.descricao,
|
descricao: asm.descricao,
|
||||||
isComposed: hasComponents ? "SIM" : "NÃO",
|
isComposed: isComposed ? "SIM" : "NÃO",
|
||||||
quantidade: asm.quantidade,
|
quantidade: asm.quantidade,
|
||||||
material: materialPrincipal,
|
material: materialPrincipal,
|
||||||
perfilPrincipal: perfilPrincipal,
|
perfilPrincipal: perfilPrincipal,
|
||||||
comprimentoMax: maxComp > 0 ? maxComp : "-",
|
comprimentoMax: maxComp > 0 ? maxComp : "-",
|
||||||
pesoUnit: finalPesoUnit > 0 ? finalPesoUnit.toFixed(2) : "-",
|
pesoUnit: pesoUnit > 0 ? pesoUnit.toFixed(2) : "-",
|
||||||
pesoTotal: finalPesoTotal > 0 ? finalPesoTotal.toFixed(2) : "-",
|
pesoTotal: totalPeso > 0 ? totalPeso.toFixed(2) : "-",
|
||||||
tratamentoSuperficial: "pintura"
|
tratamentoSuperficial: "pintura"
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -386,30 +386,57 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function processPdfLines(items) {
|
function processPdfLines(items) {
|
||||||
items.sort((a, b) => {
|
// 1. Agrupamento robusto de linhas por Clustering de Coordenada Y em cada página
|
||||||
if (a.page !== b.page) return a.page - b.page;
|
const pagesMap = new Map();
|
||||||
if (Math.abs(b.y - a.y) > 3) return b.y - a.y;
|
items.forEach((it) => {
|
||||||
return a.x - b.x;
|
if (!pagesMap.has(it.page)) pagesMap.set(it.page, []);
|
||||||
|
pagesMap.get(it.page).push(it);
|
||||||
});
|
});
|
||||||
|
|
||||||
const lines = [];
|
const lines = [];
|
||||||
let currentLine = [];
|
const Y_TOLERANCE = 6.0;
|
||||||
let lastY = null;
|
|
||||||
let lastPage = null;
|
|
||||||
|
|
||||||
items.forEach(item => {
|
const pageNumbers = Array.from(pagesMap.keys()).sort((a, b) => a - b);
|
||||||
if (lastPage === null || item.page !== lastPage || Math.abs(item.y - (lastY ?? item.y)) > 4.5) {
|
for (const pNum of pageNumbers) {
|
||||||
if (currentLine.length > 0) {
|
const pageItems = pagesMap.get(pNum);
|
||||||
lines.push(currentLine);
|
const clusters = [];
|
||||||
|
|
||||||
|
pageItems.sort((a, b) => b.y - a.y);
|
||||||
|
|
||||||
|
for (const item of pageItems) {
|
||||||
|
let bestCluster = null;
|
||||||
|
let minDiff = Infinity;
|
||||||
|
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
const diff = Math.abs(item.y - cluster.avgY);
|
||||||
|
if (diff <= Y_TOLERANCE && diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
bestCluster = cluster;
|
||||||
}
|
}
|
||||||
currentLine = [item];
|
}
|
||||||
lastY = item.y;
|
|
||||||
lastPage = item.page;
|
if (bestCluster) {
|
||||||
|
bestCluster.items.push(item);
|
||||||
|
bestCluster.ySum += item.y;
|
||||||
|
bestCluster.count += 1;
|
||||||
|
bestCluster.avgY = bestCluster.ySum / bestCluster.count;
|
||||||
} else {
|
} else {
|
||||||
currentLine.push(item);
|
clusters.push({
|
||||||
}
|
ySum: item.y,
|
||||||
|
count: 1,
|
||||||
|
avgY: item.y,
|
||||||
|
items: [item]
|
||||||
});
|
});
|
||||||
if (currentLine.length > 0) lines.push(currentLine);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters.sort((a, b) => b.avgY - a.avgY);
|
||||||
|
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
cluster.items.sort((a, b) => a.x - b.x);
|
||||||
|
lines.push(cluster.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log(`Total de linhas identificadas: ${lines.length}`);
|
log(`Total de linhas identificadas: ${lines.length}`);
|
||||||
|
|
||||||
@@ -449,6 +476,7 @@
|
|||||||
if (
|
if (
|
||||||
lineText.includes('LISTA DE PEÇAS') ||
|
lineText.includes('LISTA DE PEÇAS') ||
|
||||||
lineText.includes('LISTA ESTRUTURADA') ||
|
lineText.includes('LISTA ESTRUTURADA') ||
|
||||||
|
lineText.includes('Lista de Peças') ||
|
||||||
lineText.includes('DetailDwgExtract') ||
|
lineText.includes('DetailDwgExtract') ||
|
||||||
lineText.includes('Cliente:') ||
|
lineText.includes('Cliente:') ||
|
||||||
lineText.includes('Desenhado') ||
|
lineText.includes('Desenhado') ||
|
||||||
@@ -456,37 +484,37 @@
|
|||||||
lineText.includes('Trabalho:') ||
|
lineText.includes('Trabalho:') ||
|
||||||
lineText.includes('Kg/piece') ||
|
lineText.includes('Kg/piece') ||
|
||||||
lineText.includes('( mm)') ||
|
lineText.includes('( mm)') ||
|
||||||
|
lineText.toLowerCase().includes('peso total:') ||
|
||||||
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
|
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Linha Mestre de Peça Principal (ex: "B133-1-20 2 Pl 6x160x154" ou "B133-1-20 Pl 6x160x154")
|
// 1. Linha Mestre de Peça Principal (ex: "B134-13-1 90 W 150x13.0" ou "B134-13-2 72 W 150x13.0")
|
||||||
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)(?:\s+(\d+))?\s+([A-Za-z].*)$/);
|
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)(?:\s+(\d+))?\s+([A-Za-z].*)$/);
|
||||||
if (masterMatch) {
|
if (masterMatch) {
|
||||||
const fullMark = masterMatch[1];
|
const fullMark = masterMatch[1];
|
||||||
const inlineQuant = masterMatch[2] ? parseInt(masterMatch[2], 10) : 0;
|
const quantFromLine = masterMatch[2] ? parseInt(masterMatch[2], 10) : 1;
|
||||||
const descCandidate = masterMatch[3].trim();
|
const descCandidate = masterMatch[3].trim();
|
||||||
const parts = fullMark.split('-');
|
const parts = fullMark.split('-');
|
||||||
const pieceNumber = parts[parts.length - 1];
|
const pieceNumber = parts[parts.length - 1];
|
||||||
const pieceNumInt = parseInt(pieceNumber, 10);
|
const pieceNumInt = parseInt(pieceNumber, 10);
|
||||||
|
|
||||||
// Peça principal tem identificador < 1000 e não possui material na linha de cabeçalho da marca
|
|
||||||
if (pieceNumInt < 1000) {
|
if (pieceNumInt < 1000) {
|
||||||
const hasMaterial = KNOWN_MATERIALS.some(m => descCandidate.includes(m));
|
const hasMaterial = KNOWN_MATERIALS.some(m => descCandidate.includes(m));
|
||||||
|
|
||||||
if (!hasMaterial) {
|
if (!hasMaterial) {
|
||||||
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
|
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
|
||||||
const phaseCode = parts.length >= 3 ? parts[1] : '0';
|
const phaseCode = parts.length >= 3 ? parts[1] : (parts.length === 2 ? parts[0] : '1');
|
||||||
|
|
||||||
currentAssembly = {
|
currentAssembly = {
|
||||||
rawMark: fullMark,
|
rawMark: fullMark,
|
||||||
of: ofCode || defaultOf,
|
of: ofCode || defaultOf,
|
||||||
fase: phaseCode,
|
fase: phaseCode || '1',
|
||||||
marca: pieceNumber,
|
marca: pieceNumber,
|
||||||
numeroPeca: pieceNumber,
|
numeroPeca: pieceNumber,
|
||||||
descricao: descCandidate || 'ESTRUTURA',
|
descricao: descCandidate || 'ESTRUTURA',
|
||||||
quantidade: inlineQuant > 0 ? inlineQuant : 1,
|
quantidade: quantFromLine > 0 ? quantFromLine : 1,
|
||||||
material: '',
|
material: '',
|
||||||
comprimento: 0,
|
comprimento: 0,
|
||||||
pesoUnitario: 0,
|
pesoUnitario: 0,
|
||||||
@@ -501,7 +529,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Linha com Subtotal da Marca (ex: "81 3,07" ou "3,5 0,155" ou "2,6 0,25")
|
// 2. Linha com Subtotal da Marca
|
||||||
const tokens = lineText.split(/\s+/);
|
const tokens = lineText.split(/\s+/);
|
||||||
const allTokensNumeric = tokens.length > 0 && tokens.every(tok => /^[0-9.,]+$/.test(tok));
|
const allTokensNumeric = tokens.length > 0 && tokens.every(tok => /^[0-9.,]+$/.test(tok));
|
||||||
if (allTokensNumeric && tokens.length <= 3) {
|
if (allTokensNumeric && tokens.length <= 3) {
|
||||||
@@ -568,6 +596,7 @@
|
|||||||
const nums = numTokens.map(parseNumeric);
|
const nums = numTokens.map(parseNumeric);
|
||||||
|
|
||||||
let itemComp = 0;
|
let itemComp = 0;
|
||||||
|
let itemPUn = 0;
|
||||||
let itemPesoTot = 0;
|
let itemPesoTot = 0;
|
||||||
|
|
||||||
if (nums.length >= 1) {
|
if (nums.length >= 1) {
|
||||||
@@ -575,12 +604,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nums.length === 3) {
|
if (nums.length === 3) {
|
||||||
|
itemPUn = nums[1];
|
||||||
itemPesoTot = nums[2];
|
itemPesoTot = nums[2];
|
||||||
} else if (nums.length === 4) {
|
} else if (nums.length === 4) {
|
||||||
|
itemPUn = nums[1];
|
||||||
itemPesoTot = nums[2];
|
itemPesoTot = nums[2];
|
||||||
} else if (nums.length === 5) {
|
} else if (nums.length === 5) {
|
||||||
|
itemPUn = nums[1];
|
||||||
itemPesoTot = nums[2];
|
itemPesoTot = nums[2];
|
||||||
} else if (nums.length >= 6) {
|
} else if (nums.length >= 6) {
|
||||||
|
itemPUn = nums[2];
|
||||||
itemPesoTot = nums[3];
|
itemPesoTot = nums[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,15 +631,20 @@
|
|||||||
perfil: itemDesc || currentAssembly.descricao,
|
perfil: itemDesc || currentAssembly.descricao,
|
||||||
material: detectedMaterial || 'A36',
|
material: detectedMaterial || 'A36',
|
||||||
comprimento: itemComp,
|
comprimento: itemComp,
|
||||||
|
pesoUnitario: itemPUn,
|
||||||
pesoTotal: itemPesoTot
|
pesoTotal: itemPesoTot
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Monopeça
|
||||||
if (!currentAssembly.material && detectedMaterial) {
|
if (!currentAssembly.material && detectedMaterial) {
|
||||||
currentAssembly.material = detectedMaterial;
|
currentAssembly.material = detectedMaterial;
|
||||||
}
|
}
|
||||||
if (itemComp > 0 && (!currentAssembly.comprimento || currentAssembly.comprimento === 0)) {
|
if (itemComp > 0 && (!currentAssembly.comprimento || currentAssembly.comprimento === 0)) {
|
||||||
currentAssembly.comprimento = itemComp;
|
currentAssembly.comprimento = itemComp;
|
||||||
}
|
}
|
||||||
|
if (itemPUn > 0 && (!currentAssembly.pesoUnitario || currentAssembly.pesoUnitario === 0)) {
|
||||||
|
currentAssembly.pesoUnitario = itemPUn;
|
||||||
|
}
|
||||||
if (itemPesoTot > 0 && (!currentAssembly.pesoTotal || currentAssembly.pesoTotal === 0)) {
|
if (itemPesoTot > 0 && (!currentAssembly.pesoTotal || currentAssembly.pesoTotal === 0)) {
|
||||||
currentAssembly.pesoTotal = itemPesoTot;
|
currentAssembly.pesoTotal = itemPesoTot;
|
||||||
}
|
}
|
||||||
@@ -623,10 +661,9 @@
|
|||||||
let maxComp = asm.comprimento || 0;
|
let maxComp = asm.comprimento || 0;
|
||||||
let totalPeso = asm.subtotalPeso > 0 ? asm.subtotalPeso : asm.pesoTotal;
|
let totalPeso = asm.subtotalPeso > 0 ? asm.subtotalPeso : asm.pesoTotal;
|
||||||
|
|
||||||
let finalQuant = asm.quantidade;
|
|
||||||
|
|
||||||
if (isComposed) {
|
if (isComposed) {
|
||||||
let sumComponentsWeight = 0;
|
let sumComponentsWeight = 0;
|
||||||
|
let sumUnitComponentsWeight = 0;
|
||||||
let bestComp = asm.components[0];
|
let bestComp = asm.components[0];
|
||||||
maxComp = 0;
|
maxComp = 0;
|
||||||
|
|
||||||
@@ -636,6 +673,9 @@
|
|||||||
bestComp = c;
|
bestComp = c;
|
||||||
}
|
}
|
||||||
sumComponentsWeight += c.pesoTotal;
|
sumComponentsWeight += c.pesoTotal;
|
||||||
|
if (c.pesoUnitario && c.pesoUnitario > 0) {
|
||||||
|
sumUnitComponentsWeight += c.pesoUnitario * c.quantidade;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (bestComp) {
|
if (bestComp) {
|
||||||
@@ -648,23 +688,27 @@
|
|||||||
totalPeso = sumComponentsWeight;
|
totalPeso = sumComponentsWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se a quantidade veio 1 (ou não veio no cabeçalho), calcular relação peso total / soma peso unitário dos componentes
|
if (asm.quantidade === 1 && sumUnitComponentsWeight > 0 && totalPeso > 0) {
|
||||||
if (finalQuant <= 1 && asm.components.length > 0) {
|
const ratio = Math.round(totalPeso / sumUnitComponentsWeight);
|
||||||
const sumUnitWeights = asm.components.reduce((acc, c) => {
|
|
||||||
const unitW = c.quantidade > 0 ? (c.pesoTotal / c.quantidade) : c.pesoTotal;
|
|
||||||
return acc + unitW;
|
|
||||||
}, 0);
|
|
||||||
if (sumUnitWeights > 0 && totalPeso > sumUnitWeights * 1.3) {
|
|
||||||
const ratio = Math.round(totalPeso / sumUnitWeights);
|
|
||||||
if (ratio > 1) {
|
if (ratio > 1) {
|
||||||
finalQuant = ratio;
|
asm.quantidade = ratio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Monopeça simples
|
||||||
|
if (!totalPeso || totalPeso === 0) {
|
||||||
|
totalPeso = (asm.pesoUnitario || 0) * asm.quantidade;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asm.quantidade === 1 && asm.pesoUnitario > 0 && totalPeso > asm.pesoUnitario) {
|
||||||
|
const ratio = Math.round(totalPeso / asm.pesoUnitario);
|
||||||
|
if (ratio > 1) {
|
||||||
|
asm.quantidade = ratio;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regra de Ouro: Peso Unitário = Peso Total dividido pela quantidade da peça principal
|
const pesoUnit = asm.quantidade > 0 ? (totalPeso / asm.quantidade) : (asm.pesoUnitario || totalPeso);
|
||||||
const pesoUnit = finalQuant > 0 ? (totalPeso / finalQuant) : totalPeso;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
of: asm.of,
|
of: asm.of,
|
||||||
@@ -672,7 +716,7 @@
|
|||||||
marca: asm.marca,
|
marca: asm.marca,
|
||||||
descricao: asm.descricao,
|
descricao: asm.descricao,
|
||||||
isComposed: isComposed ? "SIM" : "NÃO",
|
isComposed: isComposed ? "SIM" : "NÃO",
|
||||||
quantidade: finalQuant,
|
quantidade: asm.quantidade,
|
||||||
material: materialPrincipal,
|
material: materialPrincipal,
|
||||||
perfilPrincipal: perfilPrincipal,
|
perfilPrincipal: perfilPrincipal,
|
||||||
comprimentoMax: maxComp > 0 ? maxComp : "-",
|
comprimentoMax: maxComp > 0 ? maxComp : "-",
|
||||||
|
|||||||
@@ -228,40 +228,81 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const processPdfLines = (items: PdfItem[]) => {
|
const processPdfLines = (items: PdfItem[]) => {
|
||||||
items.sort((a, b) => {
|
// 1. Agrupamento robusto de linhas por Clustering de Coordenada Y em cada página
|
||||||
if (a.page !== b.page) return a.page - b.page;
|
const pagesMap = new Map<number, PdfItem[]>();
|
||||||
if (Math.abs(b.y - a.y) > 3) return b.y - a.y;
|
items.forEach((it) => {
|
||||||
return a.x - b.x;
|
if (!pagesMap.has(it.page)) pagesMap.set(it.page, []);
|
||||||
|
pagesMap.get(it.page)!.push(it);
|
||||||
});
|
});
|
||||||
|
|
||||||
const lines: PdfItem[][] = [];
|
const lines: PdfItem[][] = [];
|
||||||
let currentLine: PdfItem[] = [];
|
const Y_TOLERANCE = 6.0; // Tolerância vertical em pixels para agrupar na mesma linha visual
|
||||||
let lastY: number | null = null;
|
|
||||||
let lastPage: number | null = null;
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
const pageNumbers = Array.from(pagesMap.keys()).sort((a, b) => a - b);
|
||||||
if (lastPage === null || item.page !== lastPage || Math.abs(item.y - (lastY ?? item.y)) > 4.5) {
|
for (const pNum of pageNumbers) {
|
||||||
if (currentLine.length > 0) {
|
const pageItems = pagesMap.get(pNum)!;
|
||||||
lines.push(currentLine);
|
|
||||||
|
interface LineCluster {
|
||||||
|
ySum: number;
|
||||||
|
count: number;
|
||||||
|
avgY: number;
|
||||||
|
items: PdfItem[];
|
||||||
}
|
}
|
||||||
currentLine = [item];
|
|
||||||
lastY = item.y;
|
const clusters: LineCluster[] = [];
|
||||||
lastPage = item.page;
|
|
||||||
|
// Ordena por Y decrescente (do topo da página para o rodapé)
|
||||||
|
pageItems.sort((a, b) => b.y - a.y);
|
||||||
|
|
||||||
|
for (const item of pageItems) {
|
||||||
|
let bestCluster: LineCluster | null = null;
|
||||||
|
let minDiff = Infinity;
|
||||||
|
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
const diff = Math.abs(item.y - cluster.avgY);
|
||||||
|
if (diff <= Y_TOLERANCE && diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
bestCluster = cluster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestCluster) {
|
||||||
|
bestCluster.items.push(item);
|
||||||
|
bestCluster.ySum += item.y;
|
||||||
|
bestCluster.count += 1;
|
||||||
|
bestCluster.avgY = bestCluster.ySum / bestCluster.count;
|
||||||
} else {
|
} else {
|
||||||
currentLine.push(item);
|
clusters.push({
|
||||||
}
|
ySum: item.y,
|
||||||
|
count: 1,
|
||||||
|
avgY: item.y,
|
||||||
|
items: [item]
|
||||||
});
|
});
|
||||||
if (currentLine.length > 0) lines.push(currentLine);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordena as linhas do topo para o rodapé da página
|
||||||
|
clusters.sort((a, b) => b.avgY - a.avgY);
|
||||||
|
|
||||||
|
// Dentro de cada linha, ordena da esquerda para a direita (X crescente)
|
||||||
|
for (const cluster of clusters) {
|
||||||
|
cluster.items.sort((a, b) => a.x - b.x);
|
||||||
|
lines.push(cluster.items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addLog(`Total de linhas identificadas no PDF: ${lines.length}`);
|
addLog(`Total de linhas identificadas no PDF: ${lines.length}`);
|
||||||
|
|
||||||
let defaultOf = '';
|
let defaultOf = '';
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const lineText = line.map((i) => i.text).join(' ');
|
const lineText = line.map((i) => i.text).join(' ');
|
||||||
const matchTrabalho = lineText.match(/Trabalho:\s*([A-Za-z0-9-]+)/i) || lineText.match(/Projeto:\s*([A-Za-z0-9-]+)/i);
|
const matchTrabalho =
|
||||||
|
lineText.match(/Trabalho:\s*([A-Za-z0-9-]+)/i) ||
|
||||||
|
lineText.match(/Projeto:\s*([A-Za-z0-9-]+)/i);
|
||||||
if (matchTrabalho) {
|
if (matchTrabalho) {
|
||||||
defaultOf = matchTrabalho[1].replace(/-/g, '');
|
defaultOf = matchTrabalho[1].replace(/-/g, '');
|
||||||
addLog(`OF / Trabalho identificado no cabeçalho: ${defaultOf}`);
|
addLog(`OF / Trabalho identificado no cabeçalho: ${defaultOf}`);
|
||||||
|
setHeaderOf(defaultOf);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,6 +333,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
if (
|
if (
|
||||||
lineText.includes('LISTA DE PEÇAS') ||
|
lineText.includes('LISTA DE PEÇAS') ||
|
||||||
lineText.includes('LISTA ESTRUTURADA') ||
|
lineText.includes('LISTA ESTRUTURADA') ||
|
||||||
|
lineText.includes('Lista de Peças') ||
|
||||||
lineText.includes('DetailDwgExtract') ||
|
lineText.includes('DetailDwgExtract') ||
|
||||||
lineText.includes('Cliente:') ||
|
lineText.includes('Cliente:') ||
|
||||||
lineText.includes('Desenhado') ||
|
lineText.includes('Desenhado') ||
|
||||||
@@ -299,17 +341,19 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
lineText.includes('Trabalho:') ||
|
lineText.includes('Trabalho:') ||
|
||||||
lineText.includes('Kg/piece') ||
|
lineText.includes('Kg/piece') ||
|
||||||
lineText.includes('( mm)') ||
|
lineText.includes('( mm)') ||
|
||||||
|
lineText.toLowerCase().includes('peso total:') ||
|
||||||
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
|
(lineText.toLowerCase().includes('marca') && lineText.toLowerCase().includes('nome'))
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Linha Mestre de Peça Principal (ex: "B133-1-20 Pl 6x160x154" ou "B133-1-20 2 Pl 6x160x154")
|
// 1. Linha Mestre de Peça Principal (ex: "B134-13-1 90 W 150x13.0" ou "B134-13-2 72 W 150x13.0")
|
||||||
|
// A primeira linha de cada registro contém a Marca, a Quantidade Total da Peça e o Nome/Perfil
|
||||||
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)(?:\s+(\d+))?\s+([A-Za-z].*)$/);
|
const masterMatch = lineText.match(/^([A-Za-z0-9]+(?:-\d+)+)(?:\s+(\d+))?\s+([A-Za-z].*)$/);
|
||||||
|
|
||||||
if (masterMatch) {
|
if (masterMatch) {
|
||||||
const fullMark = masterMatch[1];
|
const fullMark = masterMatch[1];
|
||||||
const quant = masterMatch[2] ? parseInt(masterMatch[2], 10) : 1;
|
const quantFromLine = masterMatch[2] ? parseInt(masterMatch[2], 10) : 1;
|
||||||
const descCandidate = masterMatch[3].trim();
|
const descCandidate = masterMatch[3].trim();
|
||||||
const parts = fullMark.split('-');
|
const parts = fullMark.split('-');
|
||||||
const pieceNumber = parts[parts.length - 1];
|
const pieceNumber = parts[parts.length - 1];
|
||||||
@@ -319,18 +363,23 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
if (pieceNumInt < 1000) {
|
if (pieceNumInt < 1000) {
|
||||||
const hasMaterial = KNOWN_MATERIALS.some((m) => descCandidate.includes(m));
|
const hasMaterial = KNOWN_MATERIALS.some((m) => descCandidate.includes(m));
|
||||||
|
|
||||||
|
// A linha mestre não possui indicação de material na descrição nem pesos
|
||||||
if (!hasMaterial) {
|
if (!hasMaterial) {
|
||||||
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
|
const ofCode = parts.length >= 2 ? parts[0] : defaultOf;
|
||||||
const phaseCode = parts.length >= 3 ? parts[1] : '0';
|
const phaseCode = parts.length >= 3 ? parts[1] : (parts.length === 2 ? parts[0] : '1');
|
||||||
|
|
||||||
|
if (phaseCode && phaseCode !== '0' && isNaN(Number(phaseCode)) === false) {
|
||||||
|
setHeaderFase(phaseCode);
|
||||||
|
}
|
||||||
|
|
||||||
currentAssembly = {
|
currentAssembly = {
|
||||||
rawMark: fullMark,
|
rawMark: fullMark,
|
||||||
of: ofCode || defaultOf,
|
of: ofCode || defaultOf,
|
||||||
fase: phaseCode,
|
fase: phaseCode || '1',
|
||||||
marca: pieceNumber,
|
marca: pieceNumber,
|
||||||
numeroPeca: pieceNumber,
|
numeroPeca: pieceNumber,
|
||||||
descricao: descCandidate || 'ESTRUTURA',
|
descricao: descCandidate || 'ESTRUTURA',
|
||||||
quantidade: quant > 0 ? quant : 1,
|
quantidade: quantFromLine > 0 ? quantFromLine : 1,
|
||||||
material: '',
|
material: '',
|
||||||
comprimento: 0,
|
comprimento: 0,
|
||||||
pesoUnitario: 0,
|
pesoUnitario: 0,
|
||||||
@@ -345,7 +394,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Linha com Subtotal da Marca (ex: "81 3,07" ou "3,5 0,155" ou "2,6 0,25")
|
// 2. Linha com Subtotal da Marca (ex: "1.973,10 101,691" ou "408,70 20,434" ou "81 3,07")
|
||||||
const tokens = lineText.split(/\s+/);
|
const tokens = lineText.split(/\s+/);
|
||||||
const allTokensNumeric = tokens.length > 0 && tokens.every((tok) => /^[0-9.,]+$/.test(tok));
|
const allTokensNumeric = tokens.length > 0 && tokens.every((tok) => /^[0-9.,]+$/.test(tok));
|
||||||
if (allTokensNumeric && tokens.length <= 3) {
|
if (allTokensNumeric && tokens.length <= 3) {
|
||||||
@@ -453,6 +502,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Monopeça (peça simples sem componentes subordinados)
|
// Monopeça (peça simples sem componentes subordinados)
|
||||||
|
// Preenche os dados técnicos da peça SEM sobrescrever a quantidade mestre
|
||||||
if (!currentAssembly.material && detectedMaterial) {
|
if (!currentAssembly.material && detectedMaterial) {
|
||||||
currentAssembly.material = detectedMaterial;
|
currentAssembly.material = detectedMaterial;
|
||||||
}
|
}
|
||||||
@@ -490,7 +540,7 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
bestComp = c;
|
bestComp = c;
|
||||||
}
|
}
|
||||||
sumComponentsWeight += c.pesoTotal;
|
sumComponentsWeight += c.pesoTotal;
|
||||||
if (c.pesoUnitario > 0) {
|
if (c.pesoUnitario && c.pesoUnitario > 0) {
|
||||||
sumUnitComponentsWeight += c.pesoUnitario * c.quantidade;
|
sumUnitComponentsWeight += c.pesoUnitario * c.quantidade;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -506,17 +556,30 @@ const AdvanceSteelConverterContent: React.FC = () => {
|
|||||||
totalPeso = sumComponentsWeight;
|
totalPeso = sumComponentsWeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detecção de quantidade de conjunto caso tenha vindo 1:
|
// Fallback de segurança para conjunto caso a quantidade na linha 1 não tenha sido lida
|
||||||
if (asm.quantidade === 1 && sumUnitComponentsWeight > 0 && totalPeso > 0) {
|
if (asm.quantidade === 1 && sumUnitComponentsWeight > 0 && totalPeso > 0) {
|
||||||
const ratio = Math.round(totalPeso / sumUnitComponentsWeight);
|
const ratio = Math.round(totalPeso / sumUnitComponentsWeight);
|
||||||
if (ratio > 1) {
|
if (ratio > 1) {
|
||||||
asm.quantidade = ratio;
|
asm.quantidade = ratio;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Monopeça simples
|
||||||
|
if (!totalPeso || totalPeso === 0) {
|
||||||
|
totalPeso = (asm.pesoUnitario || 0) * asm.quantidade;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback de segurança para monopeça caso a quantidade mestre não tenha sido lida e veio 1
|
||||||
|
if (asm.quantidade === 1 && asm.pesoUnitario > 0 && totalPeso > asm.pesoUnitario) {
|
||||||
|
const ratio = Math.round(totalPeso / asm.pesoUnitario);
|
||||||
|
if (ratio > 1) {
|
||||||
|
asm.quantidade = ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regra de Ouro: Peso Unitário = Peso Total dividido pela Quantidade da peça principal
|
// Regra de Ouro: Peso Unitário = Peso Total dividido pela Quantidade da peça principal
|
||||||
const pesoUnit = asm.quantidade > 0 ? totalPeso / asm.quantidade : totalPeso;
|
const pesoUnit = asm.quantidade > 0 ? totalPeso / asm.quantidade : (asm.pesoUnitario || totalPeso);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
of: asm.of,
|
of: asm.of,
|
||||||
|
|||||||
Reference in New Issue
Block a user