feat: gerar planilha de carga do SimpliRoute (download .xlsx)

Nova tela ADM/gerente (Administração > Planilha SimpliRoute) que gera a
planilha de importação (.xlsx) de uma operação vigente, no formato do
pt_br_example.xlsx. Preenche Latitude/Longitude reaproveitando a
geocodificação do mês anterior da mesma operação (casa nome + endereço +
supervisão; geo vem do rastreio db_reem_simplerout_2026). Pacientes novos
saem sem lat/long.

- SimpliRoute::PlanilhaCarga: monta linhas + resolve match do mês anterior
- SimpliRoute::PlanilhaCargaXlsx: builder caxlsx com cabeçalho verbatim
- Admin::PlanilhasSimpliRouteController (show/baixar) + policy admin/gerente
- Gem caxlsx (rebuild do container atualiza o Gemfile.lock)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 11:05:24 -03:00
parent fd14c057b6
commit b2695cd920
8 changed files with 376 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
<%# app/views/admin/planilhas_simpliroute/show.html.erb %>
<div class="space-y-6">
<%# Header %>
<div>
<h1 class="text-2xl font-bold text-white">Planilha SimpliRoute</h1>
<p class="text-gray-400 text-sm mt-0.5">Gera a planilha de carga (.xlsx) de uma operação vigente, já com lat/long do mês anterior</p>
</div>
<%# Flash %>
<%= render 'shared/flash' %>
<%# Explicação %>
<div class="p-4 bg-blue-900/20 border border-blue-500/40 rounded-xl text-blue-200/90 text-sm flex items-start gap-2">
<span class="text-base leading-none"></span>
<p>A <strong>Latitude/Longitude</strong> é reaproveitada do <strong>mês anterior</strong> da mesma
operação (casando nome + endereço + supervisão). Pacientes <strong>novos</strong> ou sem
correspondência saem <strong>sem</strong> lat/long — o SimpliRoute geocodifica na importação.
Se a operação não tiver mês anterior, a planilha sai inteira sem lat/long.</p>
</div>
<%# Seleção + download %>
<div class="bg-[#111] border border-white/5 rounded-2xl p-5">
<% if @operacoes_agrupadas.blank? %>
<p class="text-gray-400 text-sm">Nenhuma operação encontrada.</p>
<% else %>
<%= form_with url: baixar_admin_planilha_simpli_route_path, method: :get, data: { turbo: false },
class: 'flex flex-col sm:flex-row sm:items-end gap-3' do %>
<div class="flex-1">
<label class="block text-sm font-medium text-gray-300 mb-1.5">Operação vigente</label>
<select name="tabela" required
class="w-full bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-3 text-white
focus:outline-none focus:border-orange-500 min-h-[48px]">
<% @operacoes_agrupadas.each do |titulo, ops| %>
<optgroup label="<%= titulo %>">
<% ops.each do |op| %>
<option value="<%= op[:tabela] %>"><%= op[:label] %></option>
<% end %>
</optgroup>
<% end %>
</select>
</div>
<button type="submit"
class="px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold rounded-xl
transition-colors min-h-[48px] whitespace-nowrap">
📥 Baixar planilha
</button>
<% end %>
<% end %>
</div>
</div>

View File

@@ -58,6 +58,7 @@
<%= nav_link_to '👥 Usuários', admin_usuarios_path %>
<%= nav_link_to '⚙️ Configurações', admin_configuracoes_path %>
<%= nav_link_to '🔍 Auditoria', admin_auditoria_logs_path %>
<%= nav_link_to '📥 Planilha SimpliRoute', admin_planilha_simpli_route_path %>
<% if current_user.admin? %>
<%= nav_link_to '✏️ Editar Lançamento', admin_edicao_lancamento_path %>
<% end %>