From 0612cb0d8a746d36efe9a6aa35959ed93dd06003 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 5 Mar 2026 17:35:12 +0100 Subject: [PATCH] refactor: usa decisione di destinazione unificata per folder e zip --- services/destinationIndex.js | 46 ++++++++++++++++++++ services/folderProcessor.js | 10 +++-- services/router.js | 81 +++++++++++++++++++++++++----------- services/zipProcessor.js | 10 +++-- 4 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 services/destinationIndex.js diff --git a/services/destinationIndex.js b/services/destinationIndex.js new file mode 100644 index 0000000..f1eb6d5 --- /dev/null +++ b/services/destinationIndex.js @@ -0,0 +1,46 @@ +const fs = require('fs-extra'); +const path = require('path'); + +const CODE_FOLDER_REGEX = /^\d{3}$/; +const MAX_SCAN_DEPTH = 6; + +async function buildDestinationIndex(destinationRoot) { + const index = new Map(); + + if (!destinationRoot || !(await fs.pathExists(destinationRoot))) { + return index; + } + + async function walk(currentDir, depth) { + if (depth > MAX_SCAN_DEPTH) { + return; + } + + let entries; + try { + entries = await fs.readdir(currentDir, { withFileTypes: true }); + } catch { + return; + } + + for (const entry of entries) { + if (!entry.isDirectory()) { + continue; + } + + const fullPath = path.join(currentDir, entry.name); + if (CODE_FOLDER_REGEX.test(entry.name)) { + const rows = index.get(entry.name) || []; + rows.push(fullPath); + index.set(entry.name, rows); + } + + await walk(fullPath, depth + 1); + } + } + + await walk(destinationRoot, 0); + return index; +} + +module.exports = { buildDestinationIndex }; diff --git a/services/folderProcessor.js b/services/folderProcessor.js index b5d6a0f..a694950 100644 --- a/services/folderProcessor.js +++ b/services/folderProcessor.js @@ -1,9 +1,11 @@ const fs = require('fs-extra'); const path = require('path'); -const { findDestination, isCadFile } = require('./router'); +const { getDestinationDecision, isCadFile } = require('./router'); +const { buildDestinationIndex } = require('./destinationIndex'); async function processFolder(folder, config) { const entries = await fs.readdir(folder, { withFileTypes: true }); + const destinationIndex = await buildDestinationIndex(config?.destination); const result = { scanned: 0, copied: 0, @@ -25,15 +27,15 @@ async function processFolder(folder, config) { } const src = path.join(folder, file); - const destDir = findDestination(file, config); + const decision = getDestinationDecision(file, config, destinationIndex); + const destDir = decision.destination; if (!destDir) { result.skipped += 1; - result.details.push({ file, reason: 'Nessuna regola trovata' }); + result.details.push({ file, reason: decision.reason || 'Nessuna regola trovata' }); continue; } - await fs.ensureDir(destDir); const dest = path.join(destDir, file); await fs.copy(src, dest, { overwrite: true }); diff --git a/services/router.js b/services/router.js index 8ae1f94..b3845c3 100644 --- a/services/router.js +++ b/services/router.js @@ -1,39 +1,72 @@ const path = require('path'); -function getCadType(filename) { - const baseName = path.basename(filename).toLowerCase(); - const match = baseName.match(/\.(prt|asm|drw|dwr)(?:\.\d+)?$/); - return match ? match[1] : null; +function normalizeCadType(ext) { + const lowerExt = String(ext || '').toLowerCase(); + return lowerExt === 'drw' ? 'dwr' : lowerExt; } -function findDestination(filename, config) { - const cadType = getCadType(filename); - const globalDestination = typeof config?.destination === 'string' ? config.destination.trim() : ''; - - if (globalDestination && cadType) { - return globalDestination; +function getCadInfo(filename) { + const baseName = path.basename(filename); + const match = baseName.match(/^(.*)\.(prt|asm|drw|dwr)(?:\.([^.]+))?$/i); + if (!match) { + return null; } - for (const rule of config.rules || []) { - if ((rule.ext || '').toLowerCase() !== cadType) { - continue; - } + const rawCode = match[1]; + const type = normalizeCadType(match[2]); + const version = match[3] || ''; + const digits = rawCode.replace(/\D/g, ''); + const routingGroup = digits.length >= 5 ? digits.slice(2, 5) : null; - if (!rule.pattern) { - return rule.destination; - } + return { + code: rawCode, + digits, + type, + version, + key: `${rawCode}.${type}`, + routingGroup, + }; +} - const regex = new RegExp(rule.pattern); - if (regex.test(path.basename(filename))) { - return rule.destination; - } +function getDestinationDecision(filename, config, destinationIndex) { + const cadInfo = getCadInfo(filename); + if (!cadInfo) { + return { destination: null, reason: 'Nessuna regola trovata' }; } - return null; + if (!cadInfo.routingGroup) { + return { + destination: null, + reason: 'Nome file non conforme: servono almeno 5 cifre nel nome per ricavare la 3a-4a-5a cifra', + }; + } + + const group = cadInfo.routingGroup; + const candidates = destinationIndex?.get(group) || []; + + if (!candidates.length) { + return { + destination: null, + reason: `Sottocartella ${group} non trovata nella destinazione`, + }; + } + + if (candidates.length > 1) { + return { + destination: null, + reason: `Sottocartella ${group} trovata in ${candidates.length} percorsi diversi`, + }; + } + + return { destination: candidates[0], reason: null }; +} + +function findDestination(filename, config, destinationIndex) { + return getDestinationDecision(filename, config, destinationIndex).destination; } function isCadFile(filename) { - return Boolean(getCadType(filename)); + return Boolean(getCadInfo(filename)); } -module.exports = { findDestination, isCadFile }; +module.exports = { findDestination, getDestinationDecision, isCadFile, getCadInfo }; diff --git a/services/zipProcessor.js b/services/zipProcessor.js index b852802..a8c838e 100644 --- a/services/zipProcessor.js +++ b/services/zipProcessor.js @@ -2,10 +2,12 @@ const unzipper = require('unzipper'); const fs = require('fs-extra'); const path = require('path'); const { pipeline } = require('stream/promises'); -const { findDestination, isCadFile } = require('./router'); +const { getDestinationDecision, isCadFile } = require('./router'); +const { buildDestinationIndex } = require('./destinationIndex'); async function processZip(zipPath, config) { const stream = fs.createReadStream(zipPath).pipe(unzipper.Parse({ forceStream: true })); + const destinationIndex = await buildDestinationIndex(config?.destination); const result = { scanned: 0, copied: 0, @@ -29,16 +31,16 @@ async function processZip(zipPath, config) { continue; } - const destDir = findDestination(baseName, config); + const decision = getDestinationDecision(baseName, config, destinationIndex); + const destDir = decision.destination; if (!destDir) { result.skipped += 1; - result.details.push({ file: baseName, reason: 'Nessuna regola trovata' }); + result.details.push({ file: baseName, reason: decision.reason || 'Nessuna regola trovata' }); entry.autodrain(); continue; } - await fs.ensureDir(destDir); const dest = path.join(destDir, baseName); await pipeline(entry, fs.createWriteStream(dest));