// Re-sincroniza TODOS os atendimentos já salvos no oráculo (não só SUP, abertos e // fechados) contra /protocolos/:id/mensagens do ifbot, atualizando ErpProtocolo/ // ErpVinculoConfirmadoEm/IdClienteIxc quando o vínculo existir. Ao final, grava em // scripts/logs/ a lista dos atendimentos que GANHARAM ErpProtocolo novo (não tinham // antes) — essa lista alimenta scripts/reavaliarNovosVinculosErp.js. // // Uso: node scripts/backfillErpProtocoloTodos.js [--setor SUP] [--limite N] import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { config } from "../src/config/index.js"; import { Atendimento } from "../src/models/Atendimento.model.js"; import { importarProtocolo } from "../src/services/atendimentosService.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const setorIdx = process.argv.indexOf("--setor"); const SETOR = setorIdx > -1 ? process.argv[setorIdx + 1] : null; const limiteIdx = process.argv.indexOf("--limite"); const LIMITE = limiteIdx > -1 ? Number(process.argv[limiteIdx + 1]) : null; const CONCORRENCIA = 4; async function main() { let query = Atendimento.query() .withGraphFetched("cliente") .orderBy("Abertura", "desc"); if (SETOR) query = query.where("Setor", SETOR); if (LIMITE) query = query.limit(LIMITE); const atendimentos = await query; const total = atendimentos.length; console.log(`[backfill-erp-todos] iniciando: ${total} atendimentos${SETOR ? ` (setor ${SETOR})` : " (todos os setores)"}`); const baseUrl = config.ifbot.baseUrl.replace(/\/+$/, ""); let processados = 0; let comVinculo = 0; let erros = 0; const novosVinculos = []; const t0 = Date.now(); async function processar(a) { const erpAntes = a.cliente?.ErpProtocolo ?? null; try { const item = { Id: a.Id, Codigo: a.Codigo, Abertura: a.Abertura, Cliente: { Id: a.cliente?.Id, Nome: a.cliente?.Nome, Telefone: a.cliente?.Telefone } }; await importarProtocolo(item, baseUrl); const atualizado = await Atendimento.query() .findById(a.Id) .withGraphFetched("cliente(soVinculo)") .modifiers({ soVinculo: (q) => q.select("ErpProtocolo", "ErpVinculoConfirmadoEm", "IdClienteIxc") }); const erpDepois = atualizado?.cliente?.ErpProtocolo ?? null; if (erpDepois) { comVinculo += 1; if (!erpAntes) { novosVinculos.push({ Id: a.Id, Codigo: a.Codigo, Setor: a.Setor, ErpProtocolo: erpDepois, IdClienteIxc: atualizado.cliente.IdClienteIxc ?? null }); } } } catch (err) { erros += 1; console.warn(`[backfill-erp-todos] erro em ${a.Codigo}: ${err.message}`); } finally { processados += 1; if (processados % 100 === 0 || processados === total) { const decorridoS = Math.round((Date.now() - t0) / 1000); const ritmo = decorridoS / processados; const restamMin = Math.round(((total - processados) * ritmo) / 60); console.log( `[backfill-erp-todos] progresso: ${processados}/${total} | com vínculo: ${comVinculo} | novos: ${novosVinculos.length} | erros: ${erros} | ${decorridoS}s decorridos | ~${restamMin}min restantes` ); } } } let idx = 0; async function worker() { while (idx < atendimentos.length) { const a = atendimentos[idx++]; await processar(a); } } await Promise.all(Array.from({ length: CONCORRENCIA }, worker)); const totalS = Math.round((Date.now() - t0) / 1000); console.log( `[backfill-erp-todos] concluído: ${processados}/${total} | com vínculo: ${comVinculo} | novos vínculos: ${novosVinculos.length} | erros: ${erros} | ${totalS}s total` ); const outDir = path.resolve(__dirname, "logs"); fs.mkdirSync(outDir, { recursive: true }); const outPath = path.join(outDir, `backfill-erp-todos-novos-vinculos-${new Date().toISOString().replace(/[:.]/g, "-")}.json`); fs.writeFileSync(outPath, JSON.stringify(novosVinculos, null, 2)); console.log(`[backfill-erp-todos] ${novosVinculos.length} novos vínculos salvos em ${outPath}`); console.log(`[backfill-erp-todos] próximo passo: node scripts/reavaliarNovosVinculosErp.js --arquivo ${outPath}`); process.exit(0); } main().catch((err) => { console.error("[backfill-erp-todos] falha fatal:", err); process.exit(1); });