viabiliza/service/viabilidadeService.js

107 lines
3.4 KiB
JavaScript
Raw Normal View History

const axios = require('axios');
const fs = require('fs');
const readline = require('readline');
const XLSX = require('xlsx');
const { apiUrlBase } = require('../config/apiConfig');
const {
normalizeHeader,
isExcelFile,
detectDelimiter,
hasCepHeader,
hasAddressOrNumberHeader,
hasGeoHeaders
} = require('./fileFormat');
function findHeaderRow(rows) {
return rows.find(row => {
const hasCepNumero = hasCepHeader(row) && hasAddressOrNumberHeader(row);
const hasGeo = hasGeoHeaders(row);
return hasCepNumero || hasGeo;
}) || [];
}
function readExcelHeaders(filePath) {
const workbook = XLSX.readFile(filePath, { cellDates: false, raw: false });
const firstSheetName = workbook.SheetNames[0];
if (!firstSheetName) return [];
const rows = XLSX.utils.sheet_to_json(workbook.Sheets[firstSheetName], {
header: 1,
blankrows: true,
defval: ''
});
return findHeaderRow(rows).map(normalizeHeader);
}
async function readDelimitedHeaders(filePath) {
const instream = fs.createReadStream(filePath, { encoding: 'utf8' });
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
for await (const rawLine of rl) {
const line = rawLine.replace(/^/, '').replace(/\r$/, '');
if (!line.trim()) continue;
const headers = line.split(detectDelimiter(line)).map(normalizeHeader);
if ((hasCepHeader(headers) && hasAddressOrNumberHeader(headers)) || hasGeoHeaders(headers)) {
rl.close();
return headers;
}
}
rl.close();
return [];
}
// Verifica se os dados vindos são de CEP ou de geolocalização (aceita caminho de arquivo ou objeto).
async function discoverDataType(input) {
if (typeof input === 'string') {
const headers = isExcelFile(input)
? readExcelHeaders(input)
: await readDelimitedHeaders(input);
if (hasCepHeader(headers) && hasAddressOrNumberHeader(headers)) return 'cep';
if (hasGeoHeaders(headers)) return 'geolocalizacao';
return 'unknown';
}
if (input && typeof input === 'object') {
if (input.cep && input.numero) return 'cep';
if (input.latitude !== undefined && input.longitude !== undefined) return 'geolocalizacao';
return 'unknown';
}
return 'unknown';
}
// Consulta individual (manual / uma linha). Escolhe o endpoint conforme o tipo dos dados.
async function consultarViabilidade(data) {
const dataType = await discoverDataType(data);
const endpoint = apiUrlBase + (dataType === 'geolocalizacao' ? 'viabilidade/lat-long' : 'viabilidade');
const response = await axios.post(endpoint, data, {
// A API agora faz geocode + GeoGrid + trajeto (várias chamadas externas);
// o cep-promise também pode ser lento. 30s evita timeout prematuro.
timeout: 30000,
headers: { 'Content-Type': 'application/json' }
});
return response.data;
}
// Consulta em LOTE: envia um array de itens ({cep,numero} ou {latitude,longitude}) de uma só vez.
// Retorna o array `resultados` da API, alinhado por índice ao array enviado.
async function consultarViabilidadeLote(itens, modo) {
const endpoint = apiUrlBase + 'viabilidade/lote';
const body = { itens };
if (modo) body.modo = modo;
const response = await axios.post(endpoint, body, {
timeout: 180000,
headers: { 'Content-Type': 'application/json' }
});
const data = response.data || {};
return Array.isArray(data.resultados) ? data.resultados : [];
}
module.exports = { consultarViabilidade, consultarViabilidadeLote, discoverDataType };