Files
Reem-Notas/lib/tasks/entregas_motorista.rake
2026-08-20 15:48:47 -03:00

119 lines
5.7 KiB
Ruby

# lib/tasks/entregas_motorista.rake
#
# Lista as entregas de um motorista num período lendo o ESPELHO LOCAL
# (model Entrega / tabela db_reem_simplerout_2026) — sem tocar na API do
# SimpliRoute, logo sem precisar de SIMPLIROUTE_TOKEN.
#
# Por que aqui e não na API: a API não tem filtro de intervalo nem de motorista
# (ver bin/entregas_do_motorista), então um período vira N requisições de vários
# MB. No espelho é uma query só. O preço é a defasagem da sync — se precisar do
# estado AO VIVO, use bin/entregas_do_motorista.
#
# SÓ LEITURA: Entrega#readonly? é true; nada aqui escreve.
#
# rake "entregas:motorista[JOSE DA SILVA,2026-08-01,2026-08-14]"
# rake "entregas:motorista[JOSE DA SILVA,2026-08-01,2026-08-14,/tmp/saida.csv]"
# rake "entregas:motoristas[2026-08-01,2026-08-14]" # quem operou no período
#
# ⚠️ O nome tem que ser a grafia EXATA da coluna `driver` (do_motorista usa
# igualdade). Rode `entregas:motoristas` primeiro para ver a lista; se o nome
# passado não bater exatamente, a task sugere os parecidos em vez de devolver
# vazio silenciosamente.
#
# ⚠️ Período pelo `planned_date` (dia planejado da visita), que é o mesmo eixo
# do dashboard. As análises FINANCEIRAS usam `checkout` (data real da entrega) —
# são recortes diferentes; passe FONTE=checkout para usar esse outro eixo.
namespace :entregas do
desc 'Entregas de um motorista no período, lidas do espelho local (sem API)'
task :motorista, %i[nome inicio fim csv] => :environment do |_t, args|
nome = args[:nome].to_s.strip
inicio = args[:inicio].to_s.strip
fim = args[:fim].to_s.strip
if nome.empty? || inicio.empty? || fim.empty?
abort 'Uso: rake "entregas:motorista[NOME,YYYY-MM-DD,YYYY-MM-DD,/caminho/opcional.csv]"'
end
d1 = Date.parse(inicio)
d2 = Date.parse(fim)
abort 'Início é depois do fim.' if d1 > d2
# planned_date = dia planejado; checkout = dia em que a entrega ocorreu.
por_checkout = ENV['FONTE'].to_s.casecmp('checkout').zero?
base = Entrega.da_conta_gade
base = por_checkout ? base.no_periodo_checkout(d1, d2) : base.no_periodo(d1, d2)
entregas = base.do_motorista(nome).order(:planned_date, :checkout).to_a
if entregas.empty?
puts "Nenhuma entrega de #{nome.inspect} entre #{d1.strftime('%d/%m/%Y')} e #{d2.strftime('%d/%m/%Y')}."
# Nome quase certo é o erro mais comum aqui — mostrar o vizinho evita
# concluir "o motorista não rodou" quando na verdade a grafia divergiu.
alvo = nome.upcase
parecidos = Entrega.motoristas_ativos(inicio: d1, fim: d2)
.select { |n| n.upcase.include?(alvo) || alvo.include?(n.upcase) }
puts "Nomes parecidos no período: #{parecidos.inspect}" if parecidos.any?
puts 'Rode: rake "entregas:motoristas[' + "#{d1},#{d2}" + ']" para ver a grafia exata.'
next
end
eixo = por_checkout ? 'checkout (data real)' : 'planned_date (dia planejado)'
puts "#{'─' * 104}"
puts "#{entregas.size} entrega(s) de #{nome} · #{d1.strftime('%d/%m/%Y')} a #{d2.strftime('%d/%m/%Y')} · eixo: #{eixo}"
puts '─' * 104
printf("%-11s %-10s %-10s %-10s %-17s %-38s\n", 'DATA', 'NF', 'VEÍCULO', 'STATUS', 'CHECKOUT', 'DESTINO')
entregas.each do |e|
printf("%-11s %-10s %-10s %-10s %-17s %-38s\n",
e.planned_date&.strftime('%d/%m/%Y').to_s,
e.reference_id.to_s[0, 10],
e.vehicle.to_s[0, 10],
e.status.to_s[0, 10],
e.checkout&.strftime('%d/%m %H:%M').to_s,
e.local.to_s[0, 38])
end
puts '─' * 104
por_status = entregas.group_by { |e| e.status.to_s }.transform_values(&:size)
puts "Total: #{entregas.size} " + por_status.sort_by { |_, n| -n }.map { |s, n| "#{s}=#{n}" }.join(' ')
# "Atendidas" = foi ao local (sucesso OU insucesso) com checkout — é o
# conjunto elegível da consolidação, e não o mesmo que "entregues".
puts "Entregues (completed + checkout) ..... #{entregas.count { |e| e.concluida? && e.checkout_registrado? }}"
puts "Insucessos (failed) .................. #{entregas.count(&:falhada?)}"
puts "Atendidas (foi ao local c/ checkout) . #{entregas.count { |e| Entrega::STATUS_ATENDIDO.include?(e.status) && e.checkout_registrado? }}"
puts "Dias com entrega ..................... #{entregas.map { |e| e.planned_date&.to_date }.compact.uniq.size}"
puts "Veículos ............................. #{entregas.map(&:vehicle).compact.reject(&:empty?).uniq.join(', ')}"
if args[:csv].present?
require 'csv'
CSV.open(args[:csv], 'w') do |csv|
csv << ['Data planejada', 'NF', 'Tracking', 'Motorista', 'Veículo', 'Status', 'Checkout', 'Local', 'Endereço', 'Motivo']
entregas.each do |e|
csv << [e.planned_date, e.reference_id, e.tracking_id, e.driver, e.vehicle,
e.status, e.checkout, e.local, e.try(:address), e.try(:observation)]
end
end
puts "\nCSV gravado em #{args[:csv]}"
end
end
desc 'Motoristas que aparecem no espelho dentro do período (grafia exata + volume)'
task :motoristas, %i[inicio fim] => :environment do |_t, args|
abort 'Uso: rake "entregas:motoristas[YYYY-MM-DD,YYYY-MM-DD]"' if args[:inicio].blank? || args[:fim].blank?
d1 = Date.parse(args[:inicio])
d2 = Date.parse(args[:fim])
contagem = Entrega.da_conta_gade.no_periodo(d1, d2)
.where.not(driver: [nil, '']).group(:driver).count
if contagem.empty?
puts "Nenhuma entrega no espelho entre #{d1.strftime('%d/%m/%Y')} e #{d2.strftime('%d/%m/%Y')}."
next
end
puts "Motoristas · #{d1.strftime('%d/%m/%Y')} a #{d2.strftime('%d/%m/%Y')} (#{contagem.values.sum} entregas)"
contagem.sort_by { |_, n| -n }.each { |nome, n| puts " #{n.to_s.rjust(5)} #{nome}" }
end
end