diff --git a/config/apiConfig.js b/config/apiConfig.js index ae6ca89..1786551 100644 --- a/config/apiConfig.js +++ b/config/apiConfig.js @@ -21,5 +21,6 @@ const apiConfig = { }; const apiViabilidadeUrl = process.env.API_VIABILIDADE_URL; +const apiUrlBase = process.env.API_URL_BASE; -module.exports = { apiConfig, apiViabilidadeUrl }; \ No newline at end of file +module.exports = { apiConfig, apiViabilidadeUrl, apiUrlBase }; \ No newline at end of file diff --git a/controller/viabilidadeController.js b/controller/viabilidadeController.js index a4142b7..957775d 100644 --- a/controller/viabilidadeController.js +++ b/controller/viabilidadeController.js @@ -1,4 +1,4 @@ -const { consultarViabilidade } = require('../service/viabilidadeService'); +const { consultarViabilidade, discoverDataType } = require('../service/viabilidadeService'); const { processCsvFile, countValidLines } = require('../service/csvService'); const { getJob, createJob } = require('../service/jobStore.service'); const fs = require('fs'); @@ -18,6 +18,21 @@ async function consultarViabilidadeController(req, res) { } } +// Controlador para consultar viabilidade via geolocalização +async function consultarViaGeolocalizacaoController(req, res) { + try { + const data = req.body; + data.source = 'viabiliza.sothis.com.br'; + const result = await consultarViabilidade(data); + res.json(result); + } catch (error) { + console.error("Erro ao consultar viabilidade por geolocalização:", error && (error.message || error)); + res.status(500).json({ error: "Erro ao consultar viabilidade por geolocalização" }); + } +} + + + async function uploadCsvFile(req, res) { try { // validação simples: verifica se multer populou req.file @@ -28,6 +43,12 @@ async function uploadCsvFile(req, res) { const filePath = req.file.path; const originalName = req.file.originalname || req.file.filename || 'input.csv'; + // Verifica o tipo de dados do CSV + const dataType = await discoverDataType(filePath); + if (dataType === 'unknown') { + return res.status(400).json({ error: 'Formato de CSV inválido. Deve conter colunas CEP e Número ou Latitude e Longitude.' }); + } + // Conta as linhas válidas primeiro const total = await countValidLines(filePath); if (total === 0) { @@ -151,4 +172,4 @@ async function downloadModelController(req, res) { } } -module.exports = { consultarViabilidadeController, uploadCsvFile, getJobController, downloadCsvController, downloadModelController }; \ No newline at end of file +module.exports = { consultarViabilidadeController, uploadCsvFile, getJobController, downloadCsvController, downloadModelController, consultarViaGeolocalizacaoController }; \ No newline at end of file diff --git a/routes/viabilidadeRoutes.js b/routes/viabilidadeRoutes.js index 32b7218..17e5ac9 100644 --- a/routes/viabilidadeRoutes.js +++ b/routes/viabilidadeRoutes.js @@ -10,6 +10,9 @@ const upload = multer({ dest: path.join(__dirname, '..', 'uploads') }); // Rota para consultar viabilidade router.post('/viabilidade', viabilidadeController.consultarViabilidadeController); +// Rota para consultar por geolocalização +router.post('/geolocalizacao', viabilidadeController.consultarViaGeolocalizacaoController); + // rota de upload agora usa multer.single('csvfile') router.post('/upload', upload.single('csvfile'), viabilidadeController.uploadCsvFile); diff --git a/service/csvService.js b/service/csvService.js index 9360951..73230c5 100644 --- a/service/csvService.js +++ b/service/csvService.js @@ -1,4 +1,4 @@ -const { consultarViabilidade } = require('./viabilidadeService'); +const { consultarViabilidade, discoverDataType } = require('./viabilidadeService'); const fs = require('fs'); const path = require('path'); const readline = require('readline'); @@ -11,8 +11,9 @@ const { failJob } = require('./jobStore.service'); -// conta linhas válidas no CSV (com CEP e Número) +// conta linhas válidas no CSV (com CEP e Número ou Latitude e Longitude) async function countValidLines(inputPath) { + const dataType = await discoverDataType(inputPath); const instream = fs.createReadStream(inputPath, { encoding: 'utf8' }); const rl = readline.createInterface({ input: instream, crlfDelay: Infinity }); @@ -20,6 +21,8 @@ async function countValidLines(inputPath) { let headers = []; let idxCep = -1; let idxNumero = -1; + let idxLatitude = -1; + let idxLongitude = -1; let total = 0; for await (const rawLine of rl) { @@ -31,15 +34,22 @@ async function countValidLines(inputPath) { const lower = headers.map(h => h.toLowerCase()); idxCep = lower.indexOf('cep'); idxNumero = lower.indexOf('numero'); + idxLatitude = lower.indexOf('latitude'); + idxLongitude = lower.indexOf('longitude'); isHeader = false; continue; } const cols = line.split(';').map(c => c.trim()); - const cep = idxCep >= 0 ? String(cols[idxCep] || '').replace(/\D/g, '') : ''; - const numero = idxNumero >= 0 ? cols[idxNumero] : ''; - - if (cep && numero) total++; + if (dataType === 'cep') { + const cep = idxCep >= 0 ? String(cols[idxCep] || '').replace(/\D/g, '') : ''; + const numero = idxNumero >= 0 ? cols[idxNumero] : ''; + if (cep && numero) total++; + } else if (dataType === 'geolocalizacao') { + const latitude = idxLatitude >= 0 ? parseFloat(cols[idxLatitude]) : NaN; + const longitude = idxLongitude >= 0 ? parseFloat(cols[idxLongitude]) : NaN; + if (!isNaN(latitude) && !isNaN(longitude)) total++; + } } return total; @@ -48,6 +58,7 @@ async function countValidLines(inputPath) { // nova função: processa CSV linha a linha, chama consultarViabilidade e gera CSV de saída // Recebe jobId já criado no controller async function processCsvFile(jobId, inputPath, originalName) { + const dataType = await discoverDataType(inputPath); const total = await countValidLines(inputPath); // Job já criado no controller // const jobId = createJob(total); @@ -64,6 +75,8 @@ async function processCsvFile(jobId, inputPath, originalName) { let headers = []; let idxCep = -1; let idxNumero = -1; + let idxLatitude = -1; + let idxLongitude = -1; for await (const rawLine of rl) { const line = rawLine.replace(/\r$/, ''); // normalize CRLF @@ -78,6 +91,8 @@ async function processCsvFile(jobId, inputPath, originalName) { const lower = headers.map(h => h.toLowerCase()); idxCep = lower.indexOf('cep'); idxNumero = lower.indexOf('numero'); + idxLatitude = lower.indexOf('latitude'); + idxLongitude = lower.indexOf('longitude'); // se não encontrar, tenta variações comuns const idx = lower.indexOf('codigo postal'); @@ -95,20 +110,34 @@ async function processCsvFile(jobId, inputPath, originalName) { .map(c => c.trim()) .filter(c => c !== ''); - const cepRaw = (idxCep >= 0 && cols[idxCep]) ? cols[idxCep] : ''; - const cep = String(cepRaw).replace(/\D/g, ''); // keep digits only - const numero = (idxNumero >= 0 && cols[idxNumero]) ? cols[idxNumero] : ''; + let dataToSend = {}; + if (dataType === 'cep') { + const cepRaw = (idxCep >= 0 && cols[idxCep]) ? cols[idxCep] : ''; + const cep = String(cepRaw).replace(/\D/g, ''); // keep digits only + const numero = (idxNumero >= 0 && cols[idxNumero]) ? cols[idxNumero] : ''; - if (!cep || !numero) { - continue; // pula linha inválida + if (!cep || !numero) { + continue; // pula linha inválida + } + dataToSend = { cep, numero }; + } else if (dataType === 'geolocalizacao') { + const latitude = (idxLatitude >= 0 && cols[idxLatitude]) ? parseFloat(cols[idxLatitude]) : NaN; + const longitude = (idxLongitude >= 0 && cols[idxLongitude]) ? parseFloat(cols[idxLongitude]) : NaN; + + if (isNaN(latitude) || isNaN(longitude)) { + continue; // pula linha inválida + } + dataToSend = { latitude, longitude }; + } else { + continue; // tipo desconhecido, pula } try { - const viab = await consultarViabilidade({ cep, numero }); + const viab = await consultarViabilidade(dataToSend); const distancia = viab.distancia ?? (viab.raw && (viab.raw.distancia || viab.raw.distance)) ?? ''; - const endereco = `${(viab.logradouro) || ''}, ${(viab.bairro) || ''}, ${(viab.cidade) || ''}/${(viab.estado) || ''}, ${(viab.cep) || ''}`.trim().replace(/^[, ]+|[, ]+$/g, ''); + const endereco = viab.endereco; if (viab.naoDedicado) { var naoDedicado = "Viavel"; diff --git a/service/viabilidadeService.js b/service/viabilidadeService.js index 6103b85..f7e3cef 100644 --- a/service/viabilidadeService.js +++ b/service/viabilidadeService.js @@ -1,11 +1,20 @@ const axios = require('axios'); -const { apiConfig, apiViabilidadeUrl } = require('../config/apiConfig'); +const fs = require('fs'); +const readline = require('readline'); +const { apiConfig, apiViabilidadeUrl, apiUrlBase } = require('../config/apiConfig'); const DEFAULT_TIMEOUT = (apiConfig && apiConfig.timeoutMs) || 10000; async function consultarViabilidade(data) { try { - const response = await axios.post(apiViabilidadeUrl, data, { + const dataType = await discoverDataType(data); + let endpoint = apiUrlBase; + if (dataType === 'geolocalizacao') { + endpoint += 'viabilidade/lat-long'; + } else { + endpoint += 'viabilidade'; + } + const response = await axios.post(endpoint, data, { timeout: DEFAULT_TIMEOUT, headers: { 'Content-Type': 'application/json' } }); @@ -16,4 +25,42 @@ async function consultarViabilidade(data) { } } -module.exports = { consultarViabilidade }; \ No newline at end of file + +// Preciso de uma função para verificar se os dados vindos são de CEP ou de geolocalização +async function discoverDataType(input) { + if (typeof input === 'string') { + // Trata como filePath + const instream = fs.createReadStream(input, { encoding: 'utf8' }); + const rl = readline.createInterface({ input: instream, crlfDelay: Infinity }); + + let headers = []; + for await (const rawLine of rl) { + const line = rawLine.replace(/\r$/, ''); + if (!line.trim()) continue; + headers = line.split(';').map(h => h.trim().toLowerCase()); + break; + } + rl.close(); + + if (headers.includes('cep') && headers.includes('numero')) { + return 'cep'; + } else if (headers.includes('latitude') && headers.includes('longitude')) { + return 'geolocalizacao'; + } else { + return 'unknown'; + } + } else if (typeof input === 'object') { + // Trata como objeto de dados + if (input.cep && input.numero) { + return 'cep'; + } else if (input.latitude && input.longitude) { + return 'geolocalizacao'; + } else { + return 'unknown'; + } + } else { + return 'unknown'; + } +} + +module.exports = { consultarViabilidade, discoverDataType }; \ No newline at end of file