- Removidos os arquivos não utilizados: fetchService, geocodeService, normalizeService e retryService. - Adicionado o viabilidadeController para gerenciar consultas de viabilidade e upload de arquivos CSV. - Criadas as viabilidadeRoutes para tratar as rotas da API relacionadas à viabilidade. - Implementado o csvService para processamento de arquivos CSV e integração com as consultas de viabilidade. - Criado o jobStore.service para gerenciamento do estado dos jobs durante o processamento do CSV. - Desenvolvido o viabilidadeService para integração com a API de viabilidade.
145 lines
4.4 KiB
JavaScript
145 lines
4.4 KiB
JavaScript
const { consultarViabilidade } = require('./viabilidadeService');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const readline = require('readline');
|
|
const { once } = require('events');
|
|
const {
|
|
createJob,
|
|
incrementProcessed,
|
|
incrementErrors,
|
|
finishJob,
|
|
failJob
|
|
} = require('./jobStore.service');
|
|
|
|
// conta linhas válidas no CSV (com CEP e Número)
|
|
async function countValidLines(inputPath) {
|
|
const instream = fs.createReadStream(inputPath, { encoding: 'utf8' });
|
|
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
|
|
|
|
let isHeader = true;
|
|
let headers = [];
|
|
let idxCep = -1;
|
|
let idxNumero = -1;
|
|
let total = 0;
|
|
|
|
for await (const rawLine of rl) {
|
|
const line = rawLine.replace(/\r$/, '');
|
|
if (!line.trim()) continue;
|
|
|
|
if (isHeader) {
|
|
headers = line.split(';').map(h => h.trim());
|
|
const lower = headers.map(h => h.toLowerCase());
|
|
idxCep = lower.indexOf('cep');
|
|
idxNumero = lower.indexOf('numero');
|
|
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++;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
// nova função: processa CSV linha a linha, chama consultarViabilidade e gera CSV de saída
|
|
async function processCsvFile(inputPath) {
|
|
const total = await countValidLines(inputPath);
|
|
const jobId = createJob(total);
|
|
const baseName = path.parse(inputPath).name;
|
|
const outputFilename = `processed_${Date.now()}_${baseName}.csv`;
|
|
const outputPath = path.join(__dirname, '..', 'outputs', outputFilename);
|
|
|
|
const instream = fs.createReadStream(inputPath, { encoding: 'utf8' });
|
|
const rl = readline.createInterface({ input: instream, crlfDelay: Infinity });
|
|
const outStream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
|
|
outStream.write('\uFEFF');
|
|
|
|
let isHeader = true;
|
|
let headers = [];
|
|
let idxCep = -1;
|
|
let idxNumero = -1;
|
|
|
|
for await (const rawLine of rl) {
|
|
const line = rawLine.replace(/\r$/, ''); // normalize CRLF
|
|
if (!line.trim()) continue;
|
|
|
|
if (isHeader) {
|
|
headers = line
|
|
.split(';')
|
|
.map(h => h.trim())
|
|
.filter(h => h !== '');
|
|
|
|
const lower = headers.map(h => h.toLowerCase());
|
|
idxCep = lower.indexOf('cep');
|
|
idxNumero = lower.indexOf('numero');
|
|
|
|
// se não encontrar, tenta variações comuns
|
|
const idx = lower.indexOf('codigo postal');
|
|
if (idx !== -1) idxCep = idx;
|
|
|
|
|
|
const outHeaders = [...headers, 'Distancia', 'Endereco', 'Não Dedicado', 'Dedicado', 'Erro'];
|
|
outStream.write(outHeaders.join(';') + '\n');
|
|
isHeader = false;
|
|
continue;
|
|
}
|
|
|
|
const cols = line
|
|
.split(';')
|
|
.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] : '';
|
|
|
|
if (!cep || !numero) {
|
|
continue; // pula linha inválida
|
|
}
|
|
|
|
try {
|
|
const viab = await consultarViabilidade({ cep, numero });
|
|
|
|
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, '');
|
|
|
|
if (viab.naoDedicado) {
|
|
var naoDedicado = "Viavel";
|
|
} else {
|
|
var naoDedicado = "Não Viavel";
|
|
}
|
|
|
|
if (viab.dedicado) {
|
|
var dedicado = "Viavel";
|
|
} else {
|
|
var dedicado = "Não Viavel";
|
|
}
|
|
|
|
const error = viab.error ? String(viab.error).replace(/[\r\n;]/g, ' ') : '';
|
|
|
|
const outCols = [...cols, distancia, endereco, naoDedicado, dedicado, error];
|
|
outStream.write(outCols.join(';') + '\n');
|
|
incrementProcessed(jobId);
|
|
} catch (err) {
|
|
const errMsg = (err && (err.message || String(err))).replace(/[\r\n;]/g, ' ');
|
|
const outCols = [...cols, '', '', '', '', '', '', errMsg];
|
|
outStream.write(outCols.join(';') + '\n');
|
|
incrementErrors(jobId);
|
|
incrementProcessed(jobId);
|
|
}
|
|
}
|
|
|
|
outStream.end();
|
|
await once(outStream, 'finish');
|
|
|
|
finishJob(jobId, `/download/${path.basename(outputPath)}`);
|
|
|
|
return { jobId, outputPath };
|
|
}
|
|
|
|
module.exports = { processCsvFile }; |