Implantação da função de controle de consolidaçoes pagas e pendentes

This commit is contained in:
2026-06-17 15:49:24 -03:00
parent 292ac31c66
commit e9def7e21a
17 changed files with 526 additions and 12 deletions

View File

@@ -0,0 +1,47 @@
<%# app/views/consolidacao_mailer/pagamento_efetuado.html.erb %>
<div style="font-family: Arial, sans-serif; max-width: 520px; margin: 0 auto; background: #0a0a0a; border-radius: 12px; overflow: hidden;">
<div style="background: #0a0a0a; padding: 24px; border-left: 6px solid #f97316;">
<h1 style="color: #ffffff; margin: 0; font-size: 20px;">REEM TRANSPORTE</h1>
<p style="color: #f97316; margin: 4px 0 0; font-size: 13px;">Sistema de Logística</p>
</div>
<div style="background: #ffffff; padding: 28px;">
<h2 style="color: #0a0a0a; margin-top: 0;">Olá, <%= @user.nome.split.first %>! ✅</h2>
<p style="color: #374151; font-size: 15px; line-height: 1.6;">
Seu pagamento de entregas foi <strong>efetuado</strong> pela empresa.
</p>
<table style="width: 100%; margin: 20px 0; border-collapse: collapse;">
<tr>
<td style="background: #f3f4f6; padding: 10px 14px; font-weight: bold; color: #374151;">Período</td>
<td style="padding: 10px 14px; color: #0a0a0a;"><%= @consolidacao.nome %></td>
</tr>
<% if @forma.present? %>
<tr>
<td style="background: #f3f4f6; padding: 10px 14px; font-weight: bold; color: #374151;">Forma</td>
<td style="padding: 10px 14px; color: #0a0a0a;"><%= @forma.to_s.humanize %></td>
</tr>
<% end %>
</table>
<div style="background: #22c55e; border-radius: 10px; padding: 20px; text-align: center; margin: 20px 0;">
<p style="color: rgba(0,0,0,0.6); margin: 0; font-size: 12px; font-weight: bold;">VALOR PAGO</p>
<p style="color: #0a0a0a; margin: 6px 0 0; font-size: 32px; font-weight: 900;">
R$ <%= format('%.2f', @valor).gsub('.', ',') %>
</p>
</div>
<p style="text-align: center; margin: 24px 0;">
<a href="<%= @link_painel %>"
style="background: #0a0a0a; color: #f97316; text-decoration: none; padding: 14px 28px; border-radius: 8px; font-weight: bold; display: inline-block;">
Ver meu painel e baixar holerite →
</a>
</p>
<p style="color: #9ca3af; font-size: 12px; text-align: center;">
Entre com seu PIN de 4 números ou escaneie o QR Code do seu holerite.
</p>
</div>
</div>

View File

@@ -13,7 +13,7 @@
<%# ── Filtros ──────────────────────────────────────────── %>
<%= form_with url: consolidacoes_path, method: :get,
class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-6 grid grid-cols-1 md:grid-cols-5 gap-3' do |f| %>
class: 'bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4 mb-6 grid grid-cols-1 md:grid-cols-6 gap-3' do |f| %>
<%= f.text_field :nome, value: params[:nome], placeholder: 'Buscar por nome…',
class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 focus:border-orange-500 focus:outline-none' %>
@@ -21,6 +21,10 @@
options_for_select([['Todos os status', ''], ['📝 Rascunho', 'rascunho'], ['✅ Finalizada', 'finalizada']], params[:status]),
{}, class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
<%= f.select :pagamento,
options_for_select([['💵 Pagamento (todos)', ''], ['💰 Pagas', 'pago'], ['⏳ Parciais', 'parcial'], ['🕗 Pendentes', 'pendente']], params[:pagamento]),
{}, class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
<%= f.date_field :inicio, value: params[:inicio],
class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5' %>
@@ -44,6 +48,7 @@
<div class="flex items-center gap-3 mb-1">
<h3 class="text-white font-bold text-lg"><%= c.nome %></h3>
<%= badge_status(c.status) %>
<% if c.finalizada? %><%= badge_pagamento(c.status_pagamento) %><% end %>
</div>
<p class="text-gray-400 text-sm">
📅 <%= l c.data_inicio, format: :short %> → <%= l c.data_fim, format: :short %>

View File

@@ -1,5 +1,7 @@
<%# app/views/consolidacoes/show.html.erb %>
<% content_for :title, @consolidacao.nome %>
<% formas_pagamento = [['PIX', 'pix'], ['Transferência', 'transferencia'], ['Dinheiro', 'dinheiro']] %>
<% pode_pagar = @consolidacao.finalizada? && policy(@consolidacao).registrar_pagamento? %>
<div class="max-w-4xl mx-auto">
@@ -7,9 +9,10 @@
<%= link_to '← Consolidações', consolidacoes_path, class: 'text-gray-500 hover:text-orange-500 text-sm' %>
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3 mt-2">
<div>
<div class="flex items-center gap-3">
<div class="flex items-center gap-3 flex-wrap">
<h1 class="text-2xl font-bold text-white"><%= @consolidacao.nome %></h1>
<%= badge_status(@consolidacao.status) %>
<% if @consolidacao.finalizada? %><%= badge_pagamento(@consolidacao.status_pagamento) %><% end %>
</div>
<p class="text-gray-400 text-sm mt-1">
📅 <%= l @consolidacao.data_inicio, format: :short %> → <%= l @consolidacao.data_fim, format: :short %>
@@ -31,6 +34,28 @@
<% end %>
</div>
</div>
<%# ── Pagamento da operação inteira ─────────────────────── %>
<% if pode_pagar %>
<div class="flex flex-col sm:flex-row sm:items-center gap-2 mt-4 bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-4">
<span class="text-gray-400 text-sm font-medium mr-1">💵 Operação inteira:</span>
<% if @consolidacao.status_pagamento != :pago %>
<%= form_with url: registrar_pagamento_consolidacao_path(@consolidacao), method: :post,
data: { turbo_confirm: 'Marcar TODOS os motoristas pendentes desta consolidação como PAGOS?' },
class: 'flex items-center gap-2' do |f| %>
<%= f.select :forma_pagamento, options_for_select(formas_pagamento, 'pix'),
{}, class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-3 py-2.5 text-sm' %>
<%= f.submit '💰 Marcar tudo como pago',
class: 'bg-green-600 hover:bg-green-700 text-white font-bold px-4 py-2.5 rounded-lg text-sm cursor-pointer min-h-[44px]' %>
<% end %>
<% end %>
<% if @consolidacao.status_pagamento != :pendente %>
<%= button_to '↩ Estornar tudo', cancelar_pagamento_consolidacao_path(@consolidacao), method: :post,
data: { turbo_confirm: 'Estornar o pagamento de TODOS os motoristas?' },
class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-4 py-2.5 rounded-lg text-sm min-h-[44px]' %>
<% end %>
</div>
<% end %>
</div>
<%# Total geral %>
@@ -46,8 +71,39 @@
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-5">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<p class="text-white font-bold text-lg"><%= cm.motorista_nome %></p>
<div class="flex items-center gap-2 flex-wrap">
<p class="text-white font-bold text-lg"><%= cm.motorista_nome %></p>
<% if @consolidacao.finalizada? %>
<%= badge_pagamento(cm.pago? ? :pago : :pendente) %>
<% end %>
</div>
<p class="text-orange-500 font-black text-xl"><%= moeda(cm.valor_total) %></p>
<% if cm.pago? %>
<p class="text-green-400 text-xs mt-1">
💰 Pago em <%= l cm.pago_em, format: :short %>
<% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
<% if cm.pagador.present? %>por <%= cm.pagador.nome_display %><% end %>
</p>
<% end %>
<% if pode_pagar %>
<div class="mt-2">
<% if cm.pago? %>
<%= button_to '↩ Estornar pagamento', cancelar_pagamento_consolidacao_path(@consolidacao, consolidacao_motorista_id: cm.id), method: :post,
data: { turbo_confirm: "Estornar o pagamento de #{cm.motorista_nome}?" },
class: 'text-gray-400 hover:text-white border border-[#2a2a2a] px-3 py-2 rounded-lg text-xs min-h-[40px]' %>
<% else %>
<%= form_with url: registrar_pagamento_consolidacao_path(@consolidacao, consolidacao_motorista_id: cm.id), method: :post,
data: { turbo_confirm: "Marcar #{cm.motorista_nome} como pago?" },
class: 'flex items-center gap-2' do |f| %>
<%= f.select :forma_pagamento, options_for_select(formas_pagamento, 'pix'),
{}, class: 'bg-[#0a0a0a] border border-[#2a2a2a] text-white rounded-lg px-2 py-2 text-xs' %>
<%= f.submit '💰 Marcar pago',
class: 'bg-green-600 hover:bg-green-700 text-white font-bold px-3 py-2 rounded-lg text-xs cursor-pointer min-h-[40px]' %>
<% end %>
<% end %>
</div>
<% end %>
</div>
<div class="flex flex-wrap gap-2">
<%# Preview abre modal %>

View File

@@ -138,6 +138,79 @@
</div>
</div>
<%# ── PAGAMENTOS (pago x pendente) ──────────────────────── %>
<div class="grid grid-cols-1 xl:grid-cols-3 gap-4">
<%# Rosca pago vs a pagar (por valor) %>
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
<h2 class="text-lg font-semibold text-white mb-1">💵 Pagamentos</h2>
<p class="text-gray-400 text-sm mb-4">Consolidações finalizadas no período</p>
<% if (@pag_pago_qtd + @pag_pend_qtd).zero? %>
<div class="text-center py-8 text-gray-500">
<div class="text-3xl mb-2">🧾</div>
<p class="text-sm">Nenhuma consolidação finalizada no período</p>
</div>
<% else %>
<div class="relative h-44 mb-4">
<canvas id="grafico-pagamentos"></canvas>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between text-sm">
<span class="text-green-400">● Pago</span>
<span class="text-white font-semibold">
<%= moeda(@pag_pago_valor) %> · <%= @pag_pago_qtd %> motorista(s)
</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-gray-400">● A pagar</span>
<span class="text-white font-semibold">
<%= moeda(@pag_pend_valor) %> · <%= @pag_pend_qtd %> motorista(s)
</span>
</div>
</div>
<% end %>
</div>
<%# Tabela de consolidações com status de pagamento %>
<div class="xl:col-span-2 bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
<h2 class="text-lg font-semibold text-white mb-4">📋 Status de pagamento por consolidação</h2>
<% if @tabela_pagamentos.any? %>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="text-gray-500 text-left border-b border-white/5">
<th class="py-2 pr-3 font-medium">Consolidação</th>
<th class="py-2 px-3 font-medium">Status</th>
<th class="py-2 px-3 font-medium text-right">Pago</th>
<th class="py-2 pl-3 font-medium text-right">A pagar</th>
</tr>
</thead>
<tbody>
<% @tabela_pagamentos.each do |linha| %>
<tr class="border-b border-white/5">
<td class="py-2.5 pr-3">
<%= link_to linha[:consolidacao].nome, consolidacao_path(linha[:consolidacao]),
class: 'text-white hover:text-orange-500 font-medium' %>
</td>
<td class="py-2.5 px-3"><%= badge_pagamento(linha[:status]) %></td>
<td class="py-2.5 px-3 text-right text-green-400"><%= moeda(linha[:pago]) %></td>
<td class="py-2.5 pl-3 text-right text-gray-400"><%= moeda(linha[:pendente]) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="text-center py-8 text-gray-500">
<div class="text-3xl mb-2">📋</div>
<p class="text-sm">Nenhuma consolidação finalizada no período</p>
</div>
<% end %>
</div>
</div>
<%# Por operação %>
<% if @por_operacao.any? %>
<div class="bg-[#1a1a1a] rounded-2xl border border-white/5 p-6">
@@ -271,3 +344,47 @@
});
})();
</script>
<%# Rosca de pagamentos (pago x a pagar, por valor) %>
<script>
(function () {
const ctx = document.getElementById('grafico-pagamentos');
if (!ctx || typeof Chart === 'undefined') return;
Chart.getChart(ctx)?.destroy();
const pago = <%= @pag_pago_valor.to_f %>;
const aPagar = <%= @pag_pend_valor.to_f %>;
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Pago', 'A pagar'],
datasets: [{
data: [pago, aPagar],
backgroundColor: ['#22c55e', '#374151'],
borderColor: '#1a1a1a',
borderWidth: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '62%',
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#0a0a0a',
borderColor: '#22c55e',
borderWidth: 1,
titleColor: '#9ca3af',
bodyColor: '#ffffff',
padding: 12,
callbacks: {
label: (c) => ` ${c.label}: R$ ${c.parsed.toFixed(2).replace('.', ',')}`
}
}
}
}
});
})();
</script>

View File

@@ -6,6 +6,27 @@
<h1 class="text-2xl font-bold text-white mb-1">Olá, <%= current_user.nome.split.first %>! 👋</h1>
<p class="text-gray-400 mb-6">Aqui estão seus valores de entregas.</p>
<%# ── Aviso de pagamento recém-confirmado ───────────────── %>
<% if @pagamentos_recentes.any? %>
<div class="bg-green-600/15 border border-green-600/40 rounded-2xl p-5 mb-6">
<p class="text-green-400 font-bold text-sm uppercase tracking-wide mb-2">✅ Pagamento confirmado!</p>
<div class="space-y-2">
<% @pagamentos_recentes.each do |cm| %>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1">
<p class="text-white text-sm">
Você recebeu <span class="font-black text-green-400"><%= moeda(cm.valor_total) %></span>
pela consolidação <span class="font-semibold"><%= cm.consolidacao.nome %></span>
</p>
<p class="text-gray-400 text-xs">
💰 <%= l cm.pago_em.to_date, format: :short %>
<% if cm.forma_pagamento.present? %>· <%= cm.forma_pagamento.humanize %><% end %>
</p>
</div>
<% end %>
</div>
</div>
<% end %>
<%# ── 2 Cards grandes ─────────────────────────────────── %>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
@@ -41,14 +62,23 @@
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-5">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
<div>
<div class="flex items-center gap-2">
<div class="flex items-center gap-2 flex-wrap">
<p class="text-white font-bold"><%= c.nome %></p>
<%= badge_status(c.status) %>
<% if c.finalizada? %>
<%= badge_pagamento(cm&.pago? ? :pago : :pendente) %>
<% end %>
</div>
<p class="text-gray-400 text-sm mt-1">
📅 <%= l c.data_inicio, format: :short %> a <%= l c.data_fim, format: :short %>
</p>
<p class="text-orange-500 font-black text-xl mt-1"><%= moeda(cm&.valor_total || 0) %></p>
<% if cm&.pago? %>
<p class="text-green-400 text-xs mt-1">
💰 Pago em <%= l cm.pago_em.to_date, format: :short %>
<% if cm.forma_pagamento.present? %>via <%= cm.forma_pagamento.humanize %><% end %>
</p>
<% end %>
</div>
<% if c.finalizada? %>
<%= link_to '⬇️ Baixar meu holerite',