'use strict' const http = require('http') const https = require('https') const { URL } = require('url') const cfg = require('./config') function log (...args) { console.log(`[${new Date().toISOString().slice(11, 19)}]`, ...args) } function json (res, status, data) { res.writeHead(status, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(data)) } function parseBody (req) { return new Promise((resolve, reject) => { const chunks = [] req.on('data', c => chunks.push(c)) req.on('end', () => { const raw = Buffer.concat(chunks).toString() const ct = (req.headers['content-type'] || '').toLowerCase() if (ct.includes('application/json')) { try { resolve(JSON.parse(raw)) } catch { resolve({}) } } else if (ct.includes('urlencoded')) { resolve(Object.fromEntries(new URLSearchParams(raw))) } else { resolve(raw) } }) req.on('error', reject) }) } function httpRequest (baseUrl, path, { method = 'GET', body, headers = {}, timeout = 15000 } = {}) { return new Promise((resolve, reject) => { const u = new URL(path, baseUrl) const proto = u.protocol === 'https:' ? https : http const req = proto.request({ hostname: u.hostname, port: u.port || (u.protocol === 'https:' ? 443 : 80), path: u.pathname + u.search, method, headers: { 'Content-Type': 'application/json', ...headers }, timeout, }, (resp) => { let data = '' resp.on('data', c => { data += c }) resp.on('end', () => { try { resolve({ status: resp.statusCode, data: data ? JSON.parse(data) : null }) } catch { resolve({ status: resp.statusCode, data }) } }) }) req.on('error', reject) req.on('timeout', () => { req.destroy(); reject(new Error('Request timeout: ' + path)) }) if (body) req.write(typeof body === 'string' ? body : JSON.stringify(body)) req.end() }) } function erpFetch (path, opts = {}) { const parsed = new URL(cfg.ERP_URL + path) return new Promise((resolve, reject) => { const req = http.request({ hostname: parsed.hostname, port: parsed.port || 8000, path: parsed.pathname + parsed.search, method: opts.method || 'GET', headers: { Host: cfg.ERP_SITE, Authorization: 'token ' + cfg.ERP_TOKEN, 'Content-Type': 'application/json', ...opts.headers }, }, (res) => { let body = '' res.on('data', c => body += c) res.on('end', () => { try { resolve({ status: res.statusCode, data: JSON.parse(body) }) } catch { resolve({ status: res.statusCode, data: body }) } }) }) req.on('error', reject) if (opts.body) req.write(typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body)) req.end() }) } function erpRequest (method, path, body) { return erpFetch(path, { method, ...(body && { body }) }) } async function lookupCustomerByPhone (phone) { const digits = phone.replace(/\D/g, '').slice(-10) const fields = JSON.stringify(['name', 'customer_name', 'cell_phone', 'tel_home', 'tel_office']) for (const field of ['cell_phone', 'tel_home', 'tel_office']) { const filters = JSON.stringify([[field, 'like', '%' + digits]]) const path = `/api/resource/Customer?filters=${encodeURIComponent(filters)}&fields=${encodeURIComponent(fields)}&limit_page_length=1` try { const res = await erpFetch(path) if (res.status === 200 && res.data?.data?.length > 0) return res.data.data[0] } catch (e) { log('lookupCustomerByPhone error on ' + field + ':', e.message) } } return null } function createCommunication (fields) { return erpFetch('/api/resource/Communication', { method: 'POST', body: JSON.stringify(fields) }) } function nbiRequest (path, method = 'GET', body = null) { return httpRequest(cfg.GENIEACS_NBI_URL, path, { method, body, timeout: 15000 }) } function deepGetValue (obj, path) { const node = path.split('.').reduce((o, k) => o?.[k], obj) return node?._value !== undefined ? node._value : null } module.exports = { log, json, parseBody, httpRequest, erpFetch, erpRequest, lookupCustomerByPhone, createCommunication, nbiRequest, deepGetValue, }