Correção do relatório que é gerado para completo
This commit is contained in:
44
README.md
44
README.md
@@ -1452,13 +1452,47 @@ feito manualmente para entregar ao cliente.
|
|||||||
ENTREGAS azul `1155CC` (A–T) + vermelho `C00000` (U–Z), quadros do RESUMO em
|
ENTREGAS azul `1155CC` (A–T) + vermelho `C00000` (U–Z), quadros do RESUMO em
|
||||||
`002060`/`0070C0`/`C00000`/`A5A5A5`, status verde/laranja/azul, títulos 16pt, performance em
|
`002060`/`0070C0`/`C00000`/`A5A5A5`, status verde/laranja/azul, títulos 16pt, performance em
|
||||||
itálico %, cabeçalhos mesclados. Validado offline com caxlsx + LibreOffice.
|
itálico %, cabeçalhos mesclados. Validado offline com caxlsx + LibreOffice.
|
||||||
|
- **Links das fotos são hiperlinks clicáveis** (azul sublinhado): Nota Fiscal, Termo de
|
||||||
|
Recebimento, Foto da Fachada e Relatório de visita abrem o comprovante direto do Excel.
|
||||||
|
Qualquer célula que seja URL `http(s)` vira link (`#linkar_urls`).
|
||||||
- Arquivos: `Analytics::PlanilhaEntregas` (dados) + `Analytics::PlanilhaEntregasXlsx` (binário,
|
- Arquivos: `Analytics::PlanilhaEntregas` (dados) + `Analytics::PlanilhaEntregasXlsx` (binário,
|
||||||
caxlsx), rota `operacoes_planilha_baixar`, action `baixar_planilha`.
|
caxlsx), rota `operacoes_planilha_baixar`, action `baixar_planilha`.
|
||||||
- ⚠️ **Ressalvas:** nomes de coluna das tabelas `gade_entregas_*`/rastreio foram deduzidos com
|
|
||||||
fallback seguro (coluna inexistente sai **vazia**, sem quebrar o layout) — conferir o primeiro
|
#### Nomes de coluna — conferidos no banco real (22/07/2026)
|
||||||
download real e ajustar `COLUNAS_GADE`/`RASTREIO_COLUNAS` se algo sair em branco. Os 2
|
Os nomes eram **deduzidos** e 9 cabeçalhos nunca casavam, saindo vazios em silêncio. Os nomes
|
||||||
**gráficos embutidos** do RESUMO não são replicados. "Conferência Documentos" é controle
|
reais foram conferidos com `Entrega.column_names` e estão como 1º candidato em
|
||||||
manual (sai "A iniciar").
|
`RASTREIO_COLUNAS`; os chutes antigos continuam na lista como rede de segurança.
|
||||||
|
- Espelho usa o padrão do export pt-BR: `codrivers`, `responsible_person`, `advance`,
|
||||||
|
`start_of_time_window_1..2`, `end_of_time_window_1..2`, `required_skills`, `optional_skills`,
|
||||||
|
`comments`. Corrigidos — antes eram `copilots`, `receiver`, `early`, `window_start`, etc.
|
||||||
|
- Tabelas `gade_entregas_*` gravam a geo como **`lat`/`long`** (não `latitude`/`longitude`) —
|
||||||
|
as duas colunas da aba ENTREGAS saíam vazias. Ver `ALIAS_GADE`.
|
||||||
|
- **`Load 4` não existe** no espelho (só `load`, `load_2`, `load_3`) — sai vazia de propósito.
|
||||||
|
- Coluna **`protocolo_de_entrega`** existe no espelho e não estava em lugar nenhum: entrou como
|
||||||
|
última coluna (AU), **depois** do layout A–AT do modelo, para não deslocar nada do cliente.
|
||||||
|
- ⚠️ Vazias por **falta de dado na origem**, não por bug: `contact_phone`, `account_id`,
|
||||||
|
`account_name` (colunas existem no espelho e o sync nunca preenche — a API do SimpliRoute
|
||||||
|
TEM o valor, conferido na NF 79093) e `codrivers`/`required_skills`/`optional_skills`/
|
||||||
|
`contact_email`/`load_2`/`load_3` (vazios também no relatório baixado direto do SimpliRoute).
|
||||||
|
- ⚠️ Os 2 **gráficos embutidos** do RESUMO não são replicados. "Conferência Documentos" é
|
||||||
|
controle manual (sai "A iniciar").
|
||||||
|
|
||||||
|
#### 🔄 Número de série da base (aparelho) — job em background
|
||||||
|
A coluna AT da aba SimpliRoute é o único campo do modelo que o espelho **nunca** traz (0 de
|
||||||
|
~6.000 linhas em fev/mar/mai/jul/2026), e não dá para deduzir da `gade_entregas_*`: os seriais
|
||||||
|
de `num_serie_base` são de NFs **disjuntas** das que têm série no SimpliRoute (0 de 203 batem).
|
||||||
|
O dado só existe na API, em `extra_field_values` — o mesmo hash das fotos do motorista.
|
||||||
|
- `SincronizarSeriesAparelhoJob` varre a API por DATA (a API não filtra intervalo; cada dia são
|
||||||
|
~4 MB / ~9 s) em lotes de 6 threads e grava em **`series_aparelho`** (tabela nossa — o espelho
|
||||||
|
é read-only). Um dia que não responde vira aviso, não derruba a rodada.
|
||||||
|
- `Analytics::PlanilhaEntregas#completar_serie` completa **só** as linhas em que o espelho veio
|
||||||
|
vazio: se um dia o sync passar a preencher, o valor do espelho continua ganhando.
|
||||||
|
- Agendado em `config/schedule.rb` às **01h** (últimos 45 dias). Backfill de meses antigos:
|
||||||
|
`rake "simpli_route:series_operacao[gade_entregas_ubs_sudeste_jul_2026]"` ou
|
||||||
|
`rake "simpli_route:series_periodo[2026-07-01,2026-07-31]"`.
|
||||||
|
- Se a rodada varrer visitas e não achar **nenhuma** série, o log lista as chaves de
|
||||||
|
`extra_field_values` que vieram — é o sinal de que o campo mudou de nome e basta acrescentá-lo
|
||||||
|
em `CAMPOS_SERIE` (foi chute de nome de campo que causou o bug acima).
|
||||||
|
|
||||||
### 📊 Dashboard financeiro
|
### 📊 Dashboard financeiro
|
||||||
- **Paginação nas tabelas de pagamentos** ("realizados" e "pendentes"): 10 linhas/página com
|
- **Paginação nas tabelas de pagamentos** ("realizados" e "pendentes"): 10 linhas/página com
|
||||||
|
|||||||
149
app/jobs/sincronizar_series_aparelho_job.rb
Normal file
149
app/jobs/sincronizar_series_aparelho_job.rb
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# app/jobs/sincronizar_series_aparelho_job.rb
|
||||||
|
#
|
||||||
|
# Busca na API do SimpliRoute o "Número de Série da Base (Aparelho)" das visitas
|
||||||
|
# e grava em series_aparelho, para a planilha Entregas do cliente sair com essa
|
||||||
|
# coluna preenchida.
|
||||||
|
#
|
||||||
|
# POR QUE EM BACKGROUND: o dado só existe na API (o espelho tem a coluna e nunca
|
||||||
|
# a preenche — 0 de ~6.000 linhas em fev/mar/mai/jul/2026). A API não filtra por
|
||||||
|
# intervalo: é uma chamada POR DATA, cada uma com o dia inteiro (~4 MB / ~9 s).
|
||||||
|
# Um mês são ~25 chamadas — inviável dentro do request do download, tranquilo
|
||||||
|
# num job noturno.
|
||||||
|
#
|
||||||
|
# Roda via Whenever → `rake simpli_route:series` (últimos DIAS_PADRAO dias).
|
||||||
|
# Para meses antigos, o backfill é manual: `rake simpli_route:series_operacao[tabela]`.
|
||||||
|
class SincronizarSeriesAparelhoJob < ApplicationJob
|
||||||
|
queue_as :default
|
||||||
|
|
||||||
|
# Mesmo limite da varredura do Admin::EdicaoLancamentosController — a API do
|
||||||
|
# SimpliRoute é lenta por dia, mas aguenta alguns dias em paralelo.
|
||||||
|
MAX_PARALELO = 6
|
||||||
|
|
||||||
|
# Janela padrão da rodada agendada. Cobre o mês corrente inteiro com folga,
|
||||||
|
# que é o que o cliente pede em planilha no dia a dia.
|
||||||
|
DIAS_PADRAO = 45
|
||||||
|
|
||||||
|
# Nomes conhecidos do campo em `extra_field_values`. O 1º que vier preenchido
|
||||||
|
# ganha; se nenhum casar, cai no fallback por /seri/ (ver #serie_de) — foi
|
||||||
|
# chutar nome de campo que deixou meia planilha vazia antes.
|
||||||
|
CAMPOS_SERIE = %w[
|
||||||
|
numero_de_serie_da_base_aparelho numero_de_serie_da_base numero_serie_base
|
||||||
|
num_serie_base serie_base numero_de_serie serie
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# Devolve um resumo { datas:, visitas:, series:, falhas: } para o rake logar.
|
||||||
|
def perform(de: nil, ate: nil, operacao: nil, dias: DIAS_PADRAO)
|
||||||
|
return resumo_vazio('SIMPLIROUTE_TOKEN não configurado') unless SimpliRoute.configurado?
|
||||||
|
|
||||||
|
datas = datas_alvo(de: de, ate: ate, operacao: operacao, dias: dias)
|
||||||
|
return resumo_vazio('nenhuma data com visitas no período') if datas.empty?
|
||||||
|
|
||||||
|
visitas = varrer(datas)
|
||||||
|
gravadas = gravar(visitas)
|
||||||
|
|
||||||
|
avisar_se_nada_casou(gravadas, visitas)
|
||||||
|
{ datas: datas.size, visitas: visitas.size, series: gravadas, falhas: @falhas }
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Datas que valem uma chamada à API. Sempre saem do espelho (planned_date das
|
||||||
|
# visitas que já conhecemos) — varrer dia sem visita é chamada jogada fora.
|
||||||
|
def datas_alvo(de:, ate:, operacao:, dias:)
|
||||||
|
escopo = Entrega.where.not(planned_date: nil)
|
||||||
|
escopo = escopo.da_operacoes([operacao]) if operacao.present?
|
||||||
|
|
||||||
|
inicio, fim = if de.present? && ate.present?
|
||||||
|
[de.to_date, ate.to_date]
|
||||||
|
elsif operacao.present?
|
||||||
|
[nil, nil] # operação inteira, sem recorte de data
|
||||||
|
else
|
||||||
|
[Date.current - dias.to_i, Date.current]
|
||||||
|
end
|
||||||
|
escopo = escopo.no_periodo(inicio, fim) if inicio && fim
|
||||||
|
|
||||||
|
escopo.distinct.pluck(Arel.sql('planned_date::date')).compact.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
# Um dia que a API não responde não pode derrubar a rodada: registra e segue
|
||||||
|
# (mesma postura da varredura de NF do Admin::EdicaoLancamentosController).
|
||||||
|
def varrer(datas)
|
||||||
|
@falhas = []
|
||||||
|
mutex = Mutex.new
|
||||||
|
|
||||||
|
datas.each_slice(MAX_PARALELO).flat_map do |lote|
|
||||||
|
lote.map { |data| Thread.new { visitas_do_dia(data, mutex) } }.flat_map(&:value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def visitas_do_dia(data, mutex)
|
||||||
|
client.visitas_da_data(data)
|
||||||
|
rescue SimpliRoute::Error => e
|
||||||
|
mutex.synchronize { @falhas << { data: data.to_s, erro: e.message } }
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Grava só as visitas que TÊM série. upsert_all em vez de N inserts: uma
|
||||||
|
# operação de um mês passa de 1.000 visitas.
|
||||||
|
def gravar(visitas)
|
||||||
|
agora = Time.current
|
||||||
|
registros = visitas.filter_map do |visita|
|
||||||
|
serie = serie_de(visita)
|
||||||
|
next if serie.blank? || visita['tracking_id'].blank?
|
||||||
|
|
||||||
|
{
|
||||||
|
tracking_id: visita['tracking_id'],
|
||||||
|
reference_id: visita['reference'].to_s.presence,
|
||||||
|
numero_serie: serie,
|
||||||
|
planned_date: visita['planned_date'],
|
||||||
|
sincronizado_em: agora
|
||||||
|
}
|
||||||
|
end
|
||||||
|
# A mesma visita pode vir em duas datas varridas; upsert_all rejeita chave
|
||||||
|
# repetida no MESMO lote, então deduplica antes (fica a última).
|
||||||
|
registros = registros.index_by { |r| r[:tracking_id] }.values
|
||||||
|
return 0 if registros.empty?
|
||||||
|
|
||||||
|
SerieAparelho.upsert_all(registros, unique_by: :tracking_id, record_timestamps: true)
|
||||||
|
registros.size
|
||||||
|
end
|
||||||
|
|
||||||
|
# A série fica em `extra_field_values`, o mesmo hash das fotos do motorista.
|
||||||
|
# Ignora valor que seja URL: ali dentro convivem os campos foto_*.
|
||||||
|
def serie_de(visita)
|
||||||
|
extras = visita['extra_field_values']
|
||||||
|
return nil unless extras.is_a?(Hash)
|
||||||
|
|
||||||
|
chave = CAMPOS_SERIE.find { |c| texto_util?(extras[c]) } ||
|
||||||
|
extras.keys.find { |k| k.to_s.match?(/seri/i) && texto_util?(extras[k]) }
|
||||||
|
chave && extras[chave].to_s.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def texto_util?(valor)
|
||||||
|
v = valor.to_s.strip
|
||||||
|
v.present? && !v.match?(%r{\Ahttps?://}i)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Varreu visitas e não achou UMA série? Quase certo que o campo mudou de nome
|
||||||
|
# na API. Loga as chaves que vieram para não virar falha silenciosa de novo.
|
||||||
|
def avisar_se_nada_casou(gravadas, visitas)
|
||||||
|
return if gravadas.positive? || visitas.empty?
|
||||||
|
|
||||||
|
chaves = visitas.filter_map { |v| v['extra_field_values'] }
|
||||||
|
.select { |e| e.is_a?(Hash) }.flat_map(&:keys).uniq.sort
|
||||||
|
Rails.logger.warn(
|
||||||
|
"[series_aparelho] #{visitas.size} visitas varridas e nenhuma série encontrada. " \
|
||||||
|
"Campos vistos em extra_field_values: #{chaves.inspect}. " \
|
||||||
|
'Se o campo da série estiver nessa lista, acrescente o nome em CAMPOS_SERIE.'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def client
|
||||||
|
@client ||= SimpliRoute::Client.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def resumo_vazio(motivo)
|
||||||
|
Rails.logger.info("[series_aparelho] nada a fazer — #{motivo}.")
|
||||||
|
{ datas: 0, visitas: 0, series: 0, falhas: [], motivo: motivo }
|
||||||
|
end
|
||||||
|
end
|
||||||
23
app/models/serie_aparelho.rb
Normal file
23
app/models/serie_aparelho.rb
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# app/models/serie_aparelho.rb
|
||||||
|
#
|
||||||
|
# Número de série da base (aparelho) de UMA visita do SimpliRoute, buscado na
|
||||||
|
# API pelo SincronizarSeriesAparelhoJob.
|
||||||
|
#
|
||||||
|
# Esta é uma tabela NOSSA (gravável) — existe porque o espelho read-only
|
||||||
|
# db_reem_simplerout_2026 tem a coluna `numero_de_serie_da_base_aparelho` mas
|
||||||
|
# nunca a preenche. Ver a migration para o histórico.
|
||||||
|
class SerieAparelho < ApplicationRecord
|
||||||
|
self.table_name = 'series_aparelho'
|
||||||
|
|
||||||
|
validates :tracking_id, presence: true, uniqueness: true
|
||||||
|
validates :numero_serie, presence: true
|
||||||
|
|
||||||
|
# { tracking_id => numero_serie } das visitas informadas — uma consulta só,
|
||||||
|
# para a planilha não fazer N+1 ao preencher a coluna.
|
||||||
|
def self.por_tracking(tracking_ids)
|
||||||
|
ids = Array(tracking_ids).compact.uniq
|
||||||
|
return {} if ids.empty?
|
||||||
|
|
||||||
|
where(tracking_id: ids).pluck(:tracking_id, :numero_serie).to_h
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -166,6 +166,7 @@ module Analytics
|
|||||||
# Todas as visitas do rastreio para as NFs da operação (inclui repetidas,
|
# Todas as visitas do rastreio para as NFs da operação (inclui repetidas,
|
||||||
# como no dump original do SimpliRoute).
|
# como no dump original do SimpliRoute).
|
||||||
def linhas_rastreio
|
def linhas_rastreio
|
||||||
|
@linhas_rastreio ||= begin
|
||||||
gade = conn.quote_table_name(@tabela)
|
gade = conn.quote_table_name(@tabela)
|
||||||
sql = <<~SQL
|
sql = <<~SQL
|
||||||
SELECT r.*
|
SELECT r.*
|
||||||
@@ -173,14 +174,17 @@ module Analytics
|
|||||||
WHERE r.reference_id::text IN (SELECT nota_fiscal FROM #{gade} WHERE nota_fiscal IS NOT NULL)
|
WHERE r.reference_id::text IN (SELECT nota_fiscal FROM #{gade} WHERE nota_fiscal IS NOT NULL)
|
||||||
ORDER BY r.reference_id, r.checkout NULLS LAST
|
ORDER BY r.reference_id, r.checkout NULLS LAST
|
||||||
SQL
|
SQL
|
||||||
conn.select_all(sql).to_a
|
completar_serie(conn.select_all(sql).to_a)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# { cabeçalho do modelo => coluna real do rastreio (ou nil) }.
|
# { cabeçalho do modelo => coluna real do rastreio (ou nil) }.
|
||||||
def colunas_rastreio
|
def colunas_rastreio
|
||||||
|
@colunas_rastreio ||= begin
|
||||||
existentes = conn.columns(Entrega.table_name).map(&:name)
|
existentes = conn.columns(Entrega.table_name).map(&:name)
|
||||||
RASTREIO_COLUNAS.transform_values { |cands| cands.find { |c| existentes.include?(c) } }
|
RASTREIO_COLUNAS.transform_values { |cands| cands.find { |c| existentes.include?(c) } }
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# ── Aba RESUMO ───────────────────────────────────────────────
|
# ── Aba RESUMO ───────────────────────────────────────────────
|
||||||
# Agrega as linhas da ENTREGAS por grupo (EMAD/UBS) e coordenadoria:
|
# Agrega as linhas da ENTREGAS por grupo (EMAD/UBS) e coordenadoria:
|
||||||
@@ -224,6 +228,23 @@ module Analytics
|
|||||||
ActiveRecord::Base.connection
|
ActiveRecord::Base.connection
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# O espelho tem a coluna do número de série mas o sync NUNCA a preenche
|
||||||
|
# (0 de ~6.000 linhas em fev/mar/mai/jul/2026) e a tabela é read-only —
|
||||||
|
# quem busca o dado na API é o SincronizarSeriesAparelhoJob, que grava em
|
||||||
|
# series_aparelho. Aqui só completamos as linhas em que o espelho veio
|
||||||
|
# vazio: se um dia o sync passar a preencher, o valor dele continua valendo.
|
||||||
|
def completar_serie(linhas)
|
||||||
|
coluna = colunas_rastreio['Número de Série da Base (Aparelho)']
|
||||||
|
return linhas if coluna.nil?
|
||||||
|
|
||||||
|
pendentes = linhas.reject { |r| r[coluna].present? }
|
||||||
|
return linhas if pendentes.empty?
|
||||||
|
|
||||||
|
series = SerieAparelho.por_tracking(pendentes.map { |r| r['tracking_id'] })
|
||||||
|
pendentes.each { |r| r[coluna] = series[r['tracking_id']] if series[r['tracking_id']] }
|
||||||
|
linhas
|
||||||
|
end
|
||||||
|
|
||||||
# SELECT das colunas whitelisted; ausentes viram NULL com o mesmo alias.
|
# SELECT das colunas whitelisted; ausentes viram NULL com o mesmo alias.
|
||||||
# O nome real pode diferir da chave (ver ALIAS_GADE) — o alias no SELECT é
|
# O nome real pode diferir da chave (ver ALIAS_GADE) — o alias no SELECT é
|
||||||
# sempre a chave, então quem lê a linha não precisa saber da diferença.
|
# sempre a chave, então quem lê a linha não precisa saber da diferença.
|
||||||
|
|||||||
@@ -11,3 +11,11 @@ set :environment, ENV.fetch("RAILS_ENV", "production")
|
|||||||
every "*/30 8-18 * * *" do
|
every "*/30 8-18 * * *" do
|
||||||
rake "historico:atualizar"
|
rake "historico:atualizar"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# 01h — busca na API do SimpliRoute o número de série da base (aparelho) das
|
||||||
|
# visitas dos últimos 45 dias. É o único campo da planilha do cliente que o
|
||||||
|
# espelho não traz, e a API cobra ~9 s por DIA varrido: fora do horário
|
||||||
|
# comercial não atrapalha ninguém. Ver SincronizarSeriesAparelhoJob.
|
||||||
|
every "0 1 * * *" do
|
||||||
|
rake "simpli_route:series"
|
||||||
|
end
|
||||||
|
|||||||
26
db/migrate/20260722000001_create_series_aparelho.rb
Normal file
26
db/migrate/20260722000001_create_series_aparelho.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Guarda o "Número de Série da Base (Aparelho)" que o motorista registra no
|
||||||
|
# checkout do SimpliRoute.
|
||||||
|
#
|
||||||
|
# POR QUE UMA TABELA NOSSA: o campo existe como coluna no espelho
|
||||||
|
# (db_reem_simplerout_2026.numero_de_serie_da_base_aparelho), mas o processo que
|
||||||
|
# alimenta o espelho NUNCA o preenche — conferido em fev/mar/mai/jul/2026, 0 de
|
||||||
|
# ~6.000 linhas. O espelho é READ-ONLY (jamais escrever nele), então o job que
|
||||||
|
# busca o dado na API do SimpliRoute grava aqui, e a planilha do cliente faz o
|
||||||
|
# LEFT JOIN por tracking_id.
|
||||||
|
class CreateSeriesAparelho < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
create_table :series_aparelho do |t|
|
||||||
|
# tracking_id (SR...) é a chave do espelho — é por ele que a planilha casa.
|
||||||
|
t.string :tracking_id, null: false
|
||||||
|
t.string :reference_id # NF, só para conferência/depuração
|
||||||
|
t.string :numero_serie, null: false
|
||||||
|
t.date :planned_date # data varrida na API
|
||||||
|
t.datetime :sincronizado_em, null: false
|
||||||
|
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :series_aparelho, :tracking_id, unique: true
|
||||||
|
add_index :series_aparelho, :planned_date
|
||||||
|
end
|
||||||
|
end
|
||||||
42
lib/tasks/series_aparelho.rake
Normal file
42
lib/tasks/series_aparelho.rake
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# lib/tasks/series_aparelho.rake
|
||||||
|
#
|
||||||
|
# Sincroniza o "Número de Série da Base (Aparelho)" da API do SimpliRoute para a
|
||||||
|
# tabela series_aparelho (ver SincronizarSeriesAparelhoJob).
|
||||||
|
|
||||||
|
# Log padrão das tarefas do projeto (ver historico.rake): uma linha de início,
|
||||||
|
# uma de resultado, e o erro sobe para o cron registrar. É lambda (e não método)
|
||||||
|
# para não pendurar um `executar` global no Object.
|
||||||
|
executar = lambda do |&bloco|
|
||||||
|
puts "[#{Time.current}] Sincronizando número de série da base (SimpliRoute)..."
|
||||||
|
r = bloco.call
|
||||||
|
puts "[#{Time.current}] OK — #{r[:datas]} data(s) varrida(s), #{r[:visitas]} visita(s), #{r[:series]} série(s) gravada(s)."
|
||||||
|
puts "[#{Time.current}] AVISO — #{r[:falhas].size} data(s) sem resposta da API: #{r[:falhas].map { |f| f[:data] }.join(', ')}" if r[:falhas].present?
|
||||||
|
rescue StandardError => e
|
||||||
|
puts "[#{Time.current}] ERRO: #{e.message}"
|
||||||
|
Rails.logger.error("[simpli_route:series] #{e.message}\n#{e.backtrace.first(5).join("\n")}")
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :simpli_route do
|
||||||
|
desc 'Sincroniza o número de série da base dos últimos 45 dias (cron diário)'
|
||||||
|
task series: :environment do
|
||||||
|
executar.call { SincronizarSeriesAparelhoJob.perform_now }
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Backfill de UMA operação inteira — rake "simpli_route:series_operacao[gade_entregas_ubs_sudeste_jul_2026]"'
|
||||||
|
task :series_operacao, [:tabela] => :environment do |_t, args|
|
||||||
|
tabela = args[:tabela].to_s
|
||||||
|
if Operacao.valida?(tabela)
|
||||||
|
puts "[#{Time.current}] Backfill da operação #{tabela}..."
|
||||||
|
executar.call { SincronizarSeriesAparelhoJob.perform_now(operacao: tabela) }
|
||||||
|
else
|
||||||
|
puts "[#{Time.current}] ERRO: operação inválida (#{tabela.inspect})."
|
||||||
|
puts "Válidas: #{Operacao.nomes_validos.join(', ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Backfill por período — rake "simpli_route:series_periodo[2026-07-01,2026-07-31]"'
|
||||||
|
task :series_periodo, %i[de ate] => :environment do |_t, args|
|
||||||
|
executar.call { SincronizarSeriesAparelhoJob.perform_now(de: Date.parse(args[:de]), ate: Date.parse(args[:ate])) }
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user