sothis-contratacao-api/src/modules/contratacao/contratacao.service.js

258 lines
12 KiB
JavaScript
Raw Normal View History

const geogridService = require("../../shared/apis/geogridService.js");
const googleService = require("../../shared/apis/googleService.js");
const cepRestService = require("../../shared/apis/cepRestService.js");
const hubsoftService = require("../../shared/apis/hubsoftService.js");
2025-11-24 16:13:35 -03:00
const logger = require('../../shared/utils/logger.js');
const repository = require('./contratacao.repository.js');
const viaCepService = require("../../shared/apis/viaCepService.js");
const { ViabilidadeModel, ClientModelPf, ClientModelPj, ProspectModel } = require('./contratacao.model');
// utilitário para ler chaves do payload tolerante a ":" e espaços
const getPayloadValue = (obj, key) => {
if (!obj) return undefined;
// variações diretas
const variants = [key, `${key}:`, `${key}: `];
for (const v of variants) if (Object.prototype.hasOwnProperty.call(obj, v)) return obj[v];
// tentativa de correspondência normalizada (ignora ":" e case)
const nk = String(key).trim().toLowerCase();
for (const k of Object.keys(obj)) {
const kn = String(k).trim().replace(/:$/, '').toLowerCase();
if (kn === nk) return obj[k];
}
return undefined;
};
2025-11-24 16:13:35 -03:00
// Classe de erro personalizada para o serviço
class ServiceError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = 'ServiceError';
this.statusCode = statusCode;
}
}
// Função para verificar a viabilidade de um endereço
async function verificarViabilidade(rawViabilidadeData) {
const rawCep = rawViabilidadeData.cep;
const rawNumero = rawViabilidadeData.numero;
const source = rawViabilidadeData.source || '';
let address;
let addressString;
2025-11-24 16:13:35 -03:00
if (!rawCep || !rawNumero) {
2025-11-24 16:13:35 -03:00
logger.warn('CEP ou número não fornecidos na verificação de viabilidade.');
throw new ServiceError('CEP e número são obrigatórios.', 400);
}
// Obtém o endereço completo usando a API de CEP (viaCEP e cepRestService como fallback)
try {
address = await viaCepService.getConsultaCep(rawCep);
if (!address || !address.logradouro) {
throw new Error('Resposta inválida do viaCEP');
}
logger.info('Endereço obtido com sucesso via viaCEP', { address });
} catch (err) {
address = await cepRestService.getConsultaCep(rawCep);
if (!address || !address.logradouro) {
throw new Error('Resposta inválida do cepRestService');
}
logger.info('Endereço obtido com sucesso via cepRestService', { address });
}
if (!address) {
throw new ServiceError('Não foi possível obter endereço para o CEP informado.', 502);
}
// Obtém as coordenadas geográficas do endereço usando o Google Geocoding API
const { logradouro, bairro, localidade: city, uf: state, cep } = address;
addressString = `${logradouro}, ${rawNumero}, ${bairro}, ${city}, ${state}, ${cep}`;
logger.info('Endereço montado para geocodificação', { addressString });
const coords = await googleService.geocodeWithGoogle(addressString);
if (!coords) {
2025-11-24 16:13:35 -03:00
logger.error('Não foi possível obter coordenadas do Google Geocoding', { addressString });
throw new ServiceError('Não foi possível obter as coordenadas do endereço.', 500);
}
2025-11-24 16:13:35 -03:00
logger.info('Coordenadas obtidas com sucesso', { coords });
const viabilidade = await geogridService.consultaViabilidade(coords.lat, coords.lon);
let naoDedicado = false;
let dedicado = false;
if (viabilidade.data && viabilidade.data.distancia !== undefined) {
var distancia = viabilidade.data.distancia;
2025-11-24 16:13:35 -03:00
logger.info(`Distância para o ponto de presença: ${distancia}m`);
if (distancia <= 500) {
naoDedicado = true;
dedicado = true;
} else if (distancia <= 1000) {
naoDedicado = false;
dedicado = true;
}
2025-11-24 16:13:35 -03:00
} else {
naoDedicado = false;
dedicado = false;
var distancia = "5KM+";
logger.warn('Dados de viabilidade não contêm informação de distância', { viabilidadeData: viabilidade.data });
}
const viabilidadeResult = new ViabilidadeModel(rawViabilidadeData.nome, rawViabilidadeData.email, rawViabilidadeData.telefone, logradouro, rawNumero, bairro, city, state, cep, naoDedicado, dedicado, distancia || null);
if (source) {
return viabilidadeResult;
}
try {
await repository.insertViabilidadeData(viabilidadeResult);
logger.info('Viabilidade salva com sucesso');
} catch (err) {
logger.error('Erro ao salvar viabilidade', err);
}
return viabilidadeResult;
}
2025-11-24 16:13:35 -03:00
async function criarProspecto(rawProspectData) {
try {
2025-11-24 16:13:35 -03:00
const payload = (rawProspectData && rawProspectData.prospectData) ? rawProspectData.prospectData : rawProspectData;
const planos = {
'100 Mega + Super WiFi': { id_servico: '22' },
'200 Mega + Super WiFi': { id_servico: '94' },
'500 Mega + Super WiFi': { id_servico: '96' },
'700 Mega + Super WiFi': { id_servico: '97' },
'1 Gb + Super WiFi': { id_servico: '32' },
};
2025-11-24 16:13:35 -03:00
const planoKey = getPayloadValue(payload, 'Descrição');
const servico = planos[planoKey];
const celular = Number(String(getPayloadValue(payload, 'Celular')).replace(/\D/g, ''));
2025-11-24 16:13:35 -03:00
if (!servico) {
logger.warn('Plano não encontrado no mapeamento', { planoKey, disponíveis: Object.keys(planos) });
throw new ServiceError('Plano inválido ou não encontrado.', 400);
2025-11-24 16:13:35 -03:00
}
// parse do valor mensal de forma robusta (aceita "R$ 119,90", "119,90", etc)
const rawValor = getPayloadValue(payload, 'Valor (mensal)') || getPayloadValue(payload, 'Valor (mensal):') || getPayloadValue(payload, 'Valor');
if (rawValor) {
const sanitized = String(rawValor).replace(/[^\d,.-]/g, '').replace(/\.(?=\d{3})/g, '').replace(',', '.');
const valorNum = Number(sanitized);
if (!Number.isNaN(valorNum)) servico.valor = valorNum;
2025-11-24 16:13:35 -03:00
}
servico.id_servico = Number(servico.id_servico);
if (!servico) {
logger.warn('Plano não encontrado no mapeamento', { planoKey, disponíveis: Object.keys(planos) });
throw new ServiceError('Plano inválido ou não encontrado.', 400);
2025-11-24 16:13:35 -03:00
}
//função que troca a / da data de nascimento por -
const formatDate = (dateStr) => {
if (!dateStr) return "";
return dateStr.replace(/\//g, '-');
};
const dataNascimento = formatDate(getPayloadValue(payload, 'Data de nascimento') || "");
if (payload["CPF:"] === "") {
// Pessoa Jurídica
const prospectData = new ProspectModel(
getPayloadValue(payload, 'CEP'),
servico, // objeto { id_servico, valor }
getPayloadValue(payload, 'Razão Social'), // nomeRazaoSocial
getPayloadValue(payload, 'CNPJ'), // cpfCnpj
celular, // telefone
getPayloadValue(payload, 'E-mail'), // email
"pj", // tipoPessoa
getPayloadValue(payload, 'Bairro'), // bairro
getPayloadValue(payload, 'Logradouro'), // endereco
getPayloadValue(payload, 'Número'), // numero
"", // nomeMae (não aplicável para PJ)
getPayloadValue(payload, 'Complemento') || "", // complemento
"" // dataNascimento (não aplicável)
);
console.log(prospectData);
// serializa para JSON plano conforme toJSON()
const prospectPayload = (typeof prospectData.pjToJSON === 'function')
? prospectData.pjToJSON()
: JSON.parse(JSON.stringify(prospectData));
await hubsoftService.criarProspectHubsoft(prospectPayload);
return prospectPayload;
} else {
// Pessoa Física
const prospectData = new ProspectModel(
getPayloadValue(payload, 'CEP'),
servico, // objeto { id_servico, valor }
getPayloadValue(payload, 'Nome completo'), // nomeRazaoSocial
getPayloadValue(payload, 'CPF'), // cpfCnpj
celular, // telefone
getPayloadValue(payload, 'E-mail'), // email
"pf", // tipoPessoa
getPayloadValue(payload, 'Bairro'), // bairro
getPayloadValue(payload, 'Logradouro'), // endereco
getPayloadValue(payload, 'Número'), // numero
getPayloadValue(payload, 'Nome da mãe') || "", // nomeMae
getPayloadValue(payload, 'Complemento') || "", // complemento
dataNascimento // dataNascimento
);
console.log(prospectData);
// serializa para JSON plano conforme toJSON()
const prospectPayload = (typeof prospectData.pfToJSON === 'function')
? prospectData.pfToJSON()
: JSON.parse(JSON.stringify(prospectData));
// envia objeto plano ao hubsoft (o hubsoftService já monta { prospectData })
const result = await hubsoftService.criarProspectHubsoft(prospectPayload);
logger.info('Criando prospecto PF', { prospectPayload });
return result;
2025-11-24 16:13:35 -03:00
}
} catch (error) {
logger.error('Erro ao criar prospecto', { message: error.message, stack: error.stack });
throw new ServiceError('Erro ao criar prospecto.', 500);
}
}
module.exports = {
verificarViabilidade,
criarProspecto
};
/*
DESCRIÇÃO:
Este arquivo é a camada de "Serviço" para o módulo de contratação. Ele contém a lógica de negócio principal da aplicação, orquestrando chamadas a diferentes APIs externas e processando os dados para atender às solicitações do `contratacao.controller.js`.
A classe `ServiceError` é uma classe de erro personalizada para permitir que os serviços lancem exceções com `status codes` HTTP específicos, que podem ser capturados e tratados pelo controller.
FUNÇÕES:
- verificarViabilidade(rawCep, rawNumero):
1. É chamado pelo `contratacao.controller.js` com o CEP e o número do endereço.
2. Valida se o CEP e o número foram fornecidos, lançando um `ServiceError` (400) se não forem.
3. Chama o `cepRestService` para obter os dados do endereço a partir do CEP. Se não encontrar, lança um `ServiceError` (404).
4. Monta uma string de endereço completo e a utiliza para obter as coordenadas geográficas (latitude e longitude) através do `googleService`. Lança um erro (500) se não conseguir obter as coordenadas.
5. Com as coordenadas, chama o `geogridService` para consultar a viabilidade do serviço, que retorna a distância de um ponto de presença.
6. Com base na distância retornada pelo GeoGrid, define as flags `naoDedicado` e `dedicado` (ex: se a distância for menor que 500m, ambos são `true`).
7. Retorna um objeto contendo os dados do endereço e as flags de viabilidade para o controller.
- criarProspecto(prospectData):
1. Recebe os dados do prospecto do `contratacao.controller.js`.
2. Chama o `hubsoftService` para criar o prospecto no sistema Hubsoft.
3. Retorna o resultado da criação.
4. Em caso de erro na comunicação com o Hubsoft, lança um `ServiceError` (500).
Este serviço é o cérebro do módulo, conectando múltiplas fontes de dados externas para fornecer uma resposta unificada sobre a viabilidade e para registrar novos clientes.
*/