novo pilar tipo: termo (valor 5 no enum), lançado como uma linha por lote com manual: true e uma coluna nova quantidade. O valor_aplicado guarda o total do lote (qtd × preço), então recalcular_motorista! e todas as somas existentes seguem corretas sem mudança — linhas antigas ficam com quantidade = 1.
This commit is contained in:
BIN
Imagens para implantação /Imagem colada (20).png
Normal file
BIN
Imagens para implantação /Imagem colada (20).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -159,8 +159,9 @@ class ConsolidacaoEntregasController < ApplicationController
|
||||
|
||||
todas = @consolidacao.consolidacao_entregas.where(motorista_nome: @motorista)
|
||||
|
||||
# Agregados calculados sobre o conjunto COMPLETO (não só a página exibida)
|
||||
@resumo = todas.group(:tipo).count
|
||||
# Agregados calculados sobre o conjunto COMPLETO (não só a página exibida).
|
||||
# sum(:quantidade) (default 1) conta os termos lançados em lote como N entregas.
|
||||
@resumo = todas.group(:tipo).sum(:quantidade)
|
||||
@total = todas.sum do |c|
|
||||
c.tipo == 'desconto' ? -c.valor_aplicado : c.valor_aplicado
|
||||
end
|
||||
@@ -255,6 +256,31 @@ class ConsolidacaoEntregasController < ApplicationController
|
||||
render_apontamento_erro(e.message)
|
||||
end
|
||||
|
||||
# POST /consolidacoes/:consolidacao_id/consolidacao_entregas/apontar_termo
|
||||
# ENTREGA DE TERMO — lança um lote de N termos para o motorista. Não há NF nem
|
||||
# código de rastreio: só a quantidade, a um preço fixo configurável.
|
||||
def apontar_termo
|
||||
authorize @consolidacao, :update?
|
||||
|
||||
motorista = @consolidacao.adicionar_termos!(
|
||||
motorista: params[:motorista].to_s.strip,
|
||||
quantidade: params[:quantidade],
|
||||
user: current_user
|
||||
)
|
||||
auditar!(:editar, @consolidacao,
|
||||
dados_novos: { entrega_termo: params[:quantidade].to_i, motorista: motorista })
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: resumo_json(motorista).merge(motorista: motorista) }
|
||||
format.html do
|
||||
redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: motorista),
|
||||
notice: "#{params[:quantidade].to_i} entrega(s) de termo adicionada(s) para #{motorista}."
|
||||
end
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
render_apontamento_erro(e.message)
|
||||
end
|
||||
|
||||
# DELETE /consolidacoes/:consolidacao_id/consolidacao_entregas/remover_apontamento
|
||||
# Remove um apontamento manual — por ?id= (um pilar) ou por ?tracking_id=&motorista=
|
||||
# (todos os pilares daquela nota). Não passa pelo guard de elegibilidade, pois o
|
||||
|
||||
@@ -353,7 +353,8 @@ class ConsolidacoesController < ApplicationController
|
||||
authorize @consolidacao, :show?
|
||||
@motorista = params[:motorista]
|
||||
@entregas = @consolidacao.consolidacao_entregas.where(motorista_nome: @motorista)
|
||||
@resumo = @entregas.group(:tipo).count
|
||||
# sum(:quantidade) (default 1) conta os termos lançados em lote como N entregas.
|
||||
@resumo = @entregas.group(:tipo).sum(:quantidade)
|
||||
@total = @entregas.sum { |e| e.desconto? ? -e.valor_aplicado : e.valor_aplicado }
|
||||
render layout: false # renderiza dentro do modal (Turbo Frame)
|
||||
end
|
||||
|
||||
78
app/javascript/controllers/apontamento_termo_controller.js
Normal file
78
app/javascript/controllers/apontamento_termo_controller.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// app/javascript/controllers/apontamento_termo_controller.js
|
||||
// Stimulus — Entrega de termo. Lança um lote de N termos para o motorista da tela.
|
||||
// Entrega sem NF nem código de rastreio: só a quantidade (stepper − N +), a um
|
||||
// preço fixo configurável.
|
||||
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["modal", "quantidade", "erro"]
|
||||
static values = { apontarTermoUrl: String, motorista: String }
|
||||
|
||||
abrir() {
|
||||
this.erroTarget.classList.add("hidden")
|
||||
this.quantidadeTarget.value = "1"
|
||||
this.modalTarget.classList.remove("hidden")
|
||||
}
|
||||
|
||||
fechar() {
|
||||
this.modalTarget.classList.add("hidden")
|
||||
}
|
||||
|
||||
// Fecha ao clicar no fundo (fora do card)
|
||||
fundo(event) {
|
||||
if (event.target === this.modalTarget) this.fechar()
|
||||
}
|
||||
|
||||
// Stepper
|
||||
mais() {
|
||||
this.quantidadeTarget.value = String(this.qtdAtual() + 1)
|
||||
}
|
||||
|
||||
menos() {
|
||||
this.quantidadeTarget.value = String(Math.max(1, this.qtdAtual() - 1))
|
||||
}
|
||||
|
||||
qtdAtual() {
|
||||
const n = parseInt(this.quantidadeTarget.value, 10)
|
||||
return Number.isFinite(n) && n > 0 ? n : 1
|
||||
}
|
||||
|
||||
async adicionar(event) {
|
||||
event?.preventDefault()
|
||||
|
||||
const quantidade = this.qtdAtual()
|
||||
const motorista = this.motoristaValue.trim()
|
||||
if (!motorista) { this.mostrarErro("Motorista não identificado."); return }
|
||||
|
||||
const btn = event.currentTarget
|
||||
btn.disabled = true
|
||||
try {
|
||||
const resp = await fetch(this.apontarTermoUrlValue, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content
|
||||
},
|
||||
body: JSON.stringify({ motorista: motorista, quantidade: quantidade })
|
||||
})
|
||||
const data = await resp.json()
|
||||
if (!resp.ok || data.ok === false) {
|
||||
this.mostrarErro(data.erro || "Não foi possível adicionar as entregas de termo.")
|
||||
return
|
||||
}
|
||||
// Recarrega para refletir o valor atualizado do motorista.
|
||||
window.location.reload()
|
||||
} catch (e) {
|
||||
this.mostrarErro("Erro ao adicionar as entregas de termo. Tente novamente.")
|
||||
} finally {
|
||||
btn.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
mostrarErro(msg) {
|
||||
this.erroTarget.textContent = msg
|
||||
this.erroTarget.classList.remove("hidden")
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ class Configuracao < ApplicationRecord
|
||||
preco_bonus
|
||||
preco_desconto
|
||||
preco_extraordinaria
|
||||
preco_termo
|
||||
notificacao_whatsapp
|
||||
notificacao_email
|
||||
empresa_nome
|
||||
@@ -21,6 +22,7 @@ class Configuracao < ApplicationRecord
|
||||
preco_bonus
|
||||
preco_desconto
|
||||
preco_extraordinaria
|
||||
preco_termo
|
||||
].freeze
|
||||
|
||||
validates :chave, presence: true, inclusion: { in: CHAVES }, uniqueness: true
|
||||
@@ -52,6 +54,10 @@ class Configuracao < ApplicationRecord
|
||||
valor('preco_extraordinaria').to_f
|
||||
end
|
||||
|
||||
def self.preco_termo
|
||||
valor('preco_termo').to_f
|
||||
end
|
||||
|
||||
# Mapa usado pelo DashboardController (Fase 3)
|
||||
def self.mapa_de_precos
|
||||
{
|
||||
@@ -59,7 +65,8 @@ class Configuracao < ApplicationRecord
|
||||
retirada: preco_retirada,
|
||||
bonus: preco_bonus,
|
||||
desconto: preco_desconto,
|
||||
extraordinaria: preco_extraordinaria
|
||||
extraordinaria: preco_extraordinaria,
|
||||
termo: preco_termo
|
||||
}
|
||||
end
|
||||
|
||||
@@ -69,6 +76,7 @@ class Configuracao < ApplicationRecord
|
||||
'preco_bonus' => 'Bônus',
|
||||
'preco_desconto' => 'Desconto',
|
||||
'preco_extraordinaria' => 'Entrega Extraordinária',
|
||||
'preco_termo' => 'Entrega de Termo',
|
||||
'notificacao_whatsapp' => 'Notificação WhatsApp',
|
||||
'notificacao_email' => 'Notificação E-mail',
|
||||
'empresa_nome' => 'Nome da Empresa'
|
||||
@@ -80,6 +88,7 @@ class Configuracao < ApplicationRecord
|
||||
'preco_bonus' => '⭐',
|
||||
'preco_desconto' => '⚠️',
|
||||
'preco_extraordinaria' => '✨',
|
||||
'preco_termo' => '📄',
|
||||
'notificacao_whatsapp' => '💬',
|
||||
'notificacao_email' => '✉️',
|
||||
'empresa_nome' => '🏢'
|
||||
|
||||
@@ -244,6 +244,37 @@ class Consolidacao < ApplicationRecord
|
||||
nome
|
||||
end
|
||||
|
||||
# ── Entrega de termo ────────────────────────────────────────
|
||||
# Lança um LOTE de N "entregas de termo" — entregas sem NF nem código de rastreio
|
||||
# para atrelar (só motorista + quantidade, a um preço fixo configurável). Fica
|
||||
# numa única linha (manual: true): quantidade guarda o N e valor_aplicado guarda
|
||||
# o total do lote, então recalcular_motorista! e demais somas seguem inalteradas.
|
||||
def adicionar_termos!(motorista:, quantidade:, user:)
|
||||
nome = motorista.to_s.strip
|
||||
raise ArgumentError, 'Informe o motorista do termo.' if nome.blank?
|
||||
|
||||
qtd = quantidade.to_i
|
||||
raise ArgumentError, 'Informe uma quantidade válida de termos.' if qtd < 1
|
||||
|
||||
transaction do
|
||||
consolidacao_motoristas.find_or_create_by!(motorista_nome: nome)
|
||||
|
||||
consolidacao_entregas.create!(
|
||||
tracking_id: "TERMO-#{SecureRandom.uuid}", # sintético: não há Entrega/NF
|
||||
tipo: 'termo',
|
||||
motorista_nome: nome,
|
||||
quantidade: qtd,
|
||||
valor_aplicado: qtd * ConsolidacaoEntrega.valor_para('termo'), # total do lote
|
||||
manual: true,
|
||||
created_by: user&.id
|
||||
)
|
||||
|
||||
recalcular_motorista!(nome)
|
||||
end
|
||||
|
||||
nome
|
||||
end
|
||||
|
||||
# Recalcula o valor de um motorista (soma dos pilares, desconto subtrai) e o
|
||||
# total da consolidação. Fonte única usada pelo wizard e pelos apontamentos.
|
||||
def recalcular_motorista!(nome)
|
||||
|
||||
@@ -14,7 +14,8 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
retirada: 1,
|
||||
bonus: 2,
|
||||
desconto: 3,
|
||||
extraordinaria: 4
|
||||
extraordinaria: 4,
|
||||
termo: 5
|
||||
}
|
||||
|
||||
TIPO_CORES = {
|
||||
@@ -22,7 +23,8 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
'retirada' => { bg: 'bg-orange-800', text: 'text-white', label: 'Retirada' },
|
||||
'bonus' => { bg: 'bg-white border border-orange-500', text: 'text-black', label: 'Bônus' },
|
||||
'desconto' => { bg: 'bg-gray-900', text: 'text-white', label: 'Desconto' },
|
||||
'extraordinaria' => { bg: 'bg-purple-600', text: 'text-white', label: 'Entrega Extraordinária' }
|
||||
'extraordinaria' => { bg: 'bg-purple-600', text: 'text-white', label: 'Entrega Extraordinária' },
|
||||
'termo' => { bg: 'bg-blue-600', text: 'text-white', label: 'Entrega de Termo' }
|
||||
}.freeze
|
||||
|
||||
# Uma entrega pode ter vários pilares (Normal + Bônus + Retirada…),
|
||||
@@ -31,6 +33,7 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
validates :motorista_nome, presence: true
|
||||
validates :tipo, presence: true
|
||||
validates :valor_aplicado, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
||||
validates :quantidade, numericality: { greater_than: 0 }
|
||||
|
||||
# Busca a entrega original (leitura do banco existente)
|
||||
def entrega_original
|
||||
@@ -50,6 +53,7 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
when 'bonus' then Configuracao.preco_bonus
|
||||
when 'desconto' then Configuracao.preco_desconto
|
||||
when 'extraordinaria' then Configuracao.preco_extraordinaria
|
||||
when 'termo' then Configuracao.preco_termo
|
||||
else 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,7 +46,9 @@ module Pdf
|
||||
|
||||
secao "PROVENTOS E DESCONTOS"
|
||||
|
||||
resumo = @entregas.group(:tipo).count
|
||||
# `quantidade` (default 1) conta as entregas de termo lançadas em lote;
|
||||
# para os demais pilares equivale ao número de linhas.
|
||||
resumo = @entregas.group(:tipo).sum(:quantidade)
|
||||
# Lista os quatro pilares sempre (Normal, Retirada, Bônus, Desconto),
|
||||
# mesmo zerados, para o motorista ver o descritivo completo.
|
||||
por_tipo = ConsolidacaoEntrega::TIPO_CORES.map do |tipo, cfg|
|
||||
|
||||
@@ -70,7 +70,8 @@ module Pdf
|
||||
|
||||
def tabela_por_tipo
|
||||
soma = @entregas.reorder(nil).group(:tipo).sum(:valor_aplicado)
|
||||
qtd = @entregas.reorder(nil).group(:tipo).count
|
||||
# `quantidade` (default 1) conta os termos lançados em lote como N entregas.
|
||||
qtd = @entregas.reorder(nil).group(:tipo).sum(:quantidade)
|
||||
|
||||
linhas = [['Tipo', 'Quantidade', 'Valor']]
|
||||
ConsolidacaoEntrega::TIPO_CORES.each do |tipo, cfg|
|
||||
|
||||
@@ -37,7 +37,7 @@ module Pdf
|
||||
original&.vehicle.presence || (e.manual? ? 'manual' : '—'),
|
||||
original ? "NF #{original.numero_nf}" : (e.nf_manual.present? ? "NF #{e.nf_manual}" : '—'),
|
||||
original ? original.address.to_s : (e.obs_manual.presence || '—'),
|
||||
e.tipo_cor[:label],
|
||||
e.termo? && e.quantidade > 1 ? "#{e.tipo_cor[:label]} ×#{e.quantidade}" : e.tipo_cor[:label],
|
||||
(e.desconto? ? '-' : '') + moeda(e.valor_aplicado)
|
||||
]
|
||||
end
|
||||
@@ -67,7 +67,8 @@ module Pdf
|
||||
secao "RESUMO"
|
||||
# reorder(nil): remove o .order(:created_at) da relação — senão o Postgres
|
||||
# recusa "GROUP BY tipo ORDER BY created_at" (PG::GroupingError → 500).
|
||||
resumo = @entregas.reorder(nil).group(:tipo).count
|
||||
# sum(:quantidade) (default 1) conta os termos lançados em lote como N entregas.
|
||||
resumo = @entregas.reorder(nil).group(:tipo).sum(:quantidade)
|
||||
total = @entregas.sum { |e| e.desconto? ? -e.valor_aplicado : e.valor_aplicado }
|
||||
|
||||
resumo_linhas = [['Tipo', 'Quantidade']]
|
||||
|
||||
@@ -12,13 +12,14 @@
|
||||
</div>
|
||||
|
||||
<%# Resumo por tipo %>
|
||||
<div class="grid grid-cols-2 md:grid-cols-5 gap-3 mb-6">
|
||||
<div class="grid grid-cols-2 md:grid-cols-6 gap-3 mb-6">
|
||||
<% [
|
||||
['entrega_normal', 'Normal', 'bg-orange-500 text-black'],
|
||||
['retirada', 'Retirada', 'bg-orange-800 text-white'],
|
||||
['bonus', 'Bônus', 'bg-white text-black'],
|
||||
['desconto', 'Desconto', 'bg-black text-white border border-gray-700'],
|
||||
['extraordinaria', 'Extraordinária', 'bg-purple-600 text-white']
|
||||
['extraordinaria', 'Extraordinária', 'bg-purple-600 text-white'],
|
||||
['termo', 'Termo', 'bg-blue-600 text-white']
|
||||
].each do |tipo, label, cores| %>
|
||||
<div class="<%= cores %> rounded-xl p-4 text-center">
|
||||
<p class="font-black text-3xl"><%= @resumo[tipo] || 0 %></p>
|
||||
@@ -66,7 +67,7 @@
|
||||
<td class="px-4 py-3 text-gray-300 text-xs"><%= c.entrega_original&.address.presence || endereco_manual.presence || '—' %></td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="<%= c.tipo_cor[:bg] %> <%= c.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold">
|
||||
<%= c.tipo_cor[:label] %>
|
||||
<%= c.tipo_cor[:label] %><% if c.termo? && c.quantidade > 1 %> ×<%= c.quantidade %><% end %>
|
||||
</span>
|
||||
<% if c.manual? %>
|
||||
<span class="ml-1 bg-orange-500/15 text-orange-400 border border-orange-500/40 px-2 py-1 rounded text-xs font-bold">➕ Apontamento</span>
|
||||
|
||||
@@ -197,6 +197,61 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Entrega de termo — entrega SEM nota fiscal nem código; só motorista + quantidade. %>
|
||||
<div data-controller="apontamento-termo"
|
||||
data-apontamento-termo-apontar-termo-url-value="<%= apontar_termo_consolidacao_consolidacao_entregas_path(@consolidacao) %>"
|
||||
data-apontamento-termo-motorista-value="<%= @motorista %>"
|
||||
class="mb-4">
|
||||
<button type="button" data-action="apontamento-termo#abrir"
|
||||
class="w-full md:w-auto inline-flex items-center justify-center gap-2 bg-[#1a1a1a] hover:bg-[#2a2a2a] text-white border border-dashed border-blue-500/60 hover:border-blue-500 font-semibold px-4 py-3 rounded-lg min-h-[48px]">
|
||||
➕ Entrega de termo
|
||||
<span class="text-gray-500 text-xs font-normal hidden md:inline">(sem nota/código — só quantidade)</span>
|
||||
</button>
|
||||
|
||||
<%# Modal %>
|
||||
<div data-apontamento-termo-target="modal" data-action="click->apontamento-termo#fundo"
|
||||
class="hidden fixed inset-0 z-50 bg-black/70 flex items-center justify-center p-4">
|
||||
<div class="bg-[#1a1a1a] border border-[#2a2a2a] rounded-2xl w-full max-w-md p-6">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<h2 class="text-xl font-bold text-white">📄 Entrega de termo</h2>
|
||||
<button type="button" data-action="apontamento-termo#fechar"
|
||||
class="text-gray-500 hover:text-white text-2xl leading-none">✕</button>
|
||||
</div>
|
||||
<p class="text-gray-400 text-sm mb-4">Entrega sem nota fiscal nem código de rastreio. Informe quantos termos foram entregues.</p>
|
||||
|
||||
<p data-apontamento-termo-target="erro" class="hidden bg-red-500/10 text-red-400 border border-red-500/30 rounded-lg px-3 py-2 text-sm mb-3"></p>
|
||||
|
||||
<%# Motorista (contexto da tela) + preço unitário %>
|
||||
<div class="flex items-center justify-between bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg px-4 py-3 mb-4">
|
||||
<div>
|
||||
<p class="text-gray-500 text-xs">Motorista</p>
|
||||
<p class="text-white font-semibold">🚚 <%= @motorista %></p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<p class="text-gray-500 text-xs">Preço por termo</p>
|
||||
<p class="text-blue-400 font-semibold"><%= moeda(Configuracao.preco_termo) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Stepper de quantidade %>
|
||||
<label class="block text-gray-400 text-sm mb-2">Quantidade de termos</label>
|
||||
<div class="flex items-center justify-center gap-3 mb-5">
|
||||
<button type="button" data-action="apontamento-termo#menos"
|
||||
class="w-12 h-12 rounded-lg bg-[#0a0a0a] border border-[#2a2a2a] hover:border-blue-500 text-white text-2xl font-bold leading-none">−</button>
|
||||
<input data-apontamento-termo-target="quantidade" type="number" inputmode="numeric" min="1" value="1" readonly
|
||||
class="w-24 text-center bg-[#0a0a0a] border border-[#2a2a2a] text-white text-2xl font-bold rounded-lg py-2.5 focus:outline-none">
|
||||
<button type="button" data-action="apontamento-termo#mais"
|
||||
class="w-12 h-12 rounded-lg bg-[#0a0a0a] border border-[#2a2a2a] hover:border-blue-500 text-white text-2xl font-bold leading-none">+</button>
|
||||
</div>
|
||||
|
||||
<button type="button" data-action="apontamento-termo#adicionar"
|
||||
class="w-full bg-green-600 hover:bg-green-500 text-white font-bold py-3.5 rounded-lg min-h-[48px]">
|
||||
Adicionar termos ✓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4">
|
||||
|
||||
@@ -159,13 +159,14 @@
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[#2a2a2a]">
|
||||
<% @apontamentos.each do |(motorista, _tracking), pilares| %>
|
||||
<% entrega = pilares.first.entrega_original %>
|
||||
<% eh_termo = pilares.first.termo? %>
|
||||
<% entrega = eh_termo ? nil : pilares.first.entrega_original %>
|
||||
<% nf_exibida = entrega&.numero_nf || pilares.first.nf_manual %>
|
||||
<% obs_manual = pilares.first.obs_manual %>
|
||||
<% total = pilares.sum { |p| p.desconto? ? -p.valor_aplicado : p.valor_aplicado } %>
|
||||
<tr>
|
||||
<td class="px-4 py-3">
|
||||
<p class="text-white font-bold">NF <%= nf_exibida.presence || '—' %></p>
|
||||
<p class="text-white font-bold"><%= eh_termo ? '📄 Entrega de termo' : "NF #{nf_exibida.presence || '—'}" %></p>
|
||||
<p class="text-gray-400 text-xs">
|
||||
<%= motorista %><% if entrega&.vehicle.present? %> · <%= entrega.vehicle %><% end %>
|
||||
<% if entrega.nil? && obs_manual.present? %> · <%= obs_manual %><% end %>
|
||||
@@ -174,7 +175,7 @@
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<% pilares.each do |p| %>
|
||||
<span class="<%= p.tipo_cor[:bg] %> <%= p.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold"><%= p.tipo_cor[:label] %></span>
|
||||
<span class="<%= p.tipo_cor[:bg] %> <%= p.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold"><%= p.tipo_cor[:label] %><% if p.termo? && p.quantidade > 1 %> ×<%= p.quantidade %><% end %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
@@ -184,7 +185,7 @@
|
||||
<%= button_to '✕ Remover',
|
||||
remover_apontamento_consolidacao_consolidacao_entregas_path(@consolidacao, tracking_id: pilares.first.tracking_id, motorista: motorista),
|
||||
method: :delete,
|
||||
form: { data: { turbo_confirm: "Remover o apontamento da NF #{nf_exibida.presence || '—'} de #{motorista}?" } },
|
||||
form: { data: { turbo_confirm: eh_termo ? "Remover a entrega de termo de #{motorista}?" : "Remover o apontamento da NF #{nf_exibida.presence || '—'} de #{motorista}?" } },
|
||||
class: 'text-gray-400 hover:text-red-400 border border-[#2a2a2a] hover:border-red-500/50 px-3 py-2 rounded-lg text-xs min-h-[40px]' %>
|
||||
</td>
|
||||
<% end %>
|
||||
|
||||
@@ -59,6 +59,7 @@ Rails.application.routes.draw do
|
||||
get :buscar_nf # JSON — busca a NF na SimpleRoute (apontamento manual)
|
||||
post :apontar # cria apontamento manual a partir da NF
|
||||
post :apontar_manual # apontamento 100% manual (sem rastreio)
|
||||
post :apontar_termo # lança N entregas de termo (sem NF/código)
|
||||
delete :remover_apontamento # remove um apontamento manual (por id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Adiciona `quantidade` aos pilares da consolidação. Usado pelas "entregas de
|
||||
# termo" (lote de N termos lançado numa única linha). Linhas existentes/demais
|
||||
# pilares ficam com quantidade = 1 e seguem inalteradas — o valor_aplicado já
|
||||
# guarda o total da linha, então as somas existentes continuam corretas.
|
||||
class AddQuantidadeToConsolidacaoEntregas < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :consolidacao_entregas, :quantidade, :integer, null: false, default: 1
|
||||
end
|
||||
end
|
||||
18
db/migrate/20260626000002_add_preco_termo_configuracao.rb
Normal file
18
db/migrate/20260626000002_add_preco_termo_configuracao.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# Cria a configuração de preço "Entrega de Termo" em bancos já existentes, para
|
||||
# que apareça na tela de Configurações. Idempotente — não sobrescreve se o admin
|
||||
# já tiver definido um valor.
|
||||
class AddPrecoTermoConfiguracao < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
return if Configuracao.exists?(chave: 'preco_termo')
|
||||
|
||||
Configuracao.create!(
|
||||
chave: 'preco_termo',
|
||||
valor: '15.00',
|
||||
descricao: 'Valor pago por entrega de termo (R$)'
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
Configuracao.where(chave: 'preco_termo').delete_all
|
||||
end
|
||||
end
|
||||
@@ -29,6 +29,7 @@ configs = [
|
||||
{ chave: 'preco_bonus', valor: '10.00', descricao: 'Valor de bônus por meta/critério (R$)' },
|
||||
{ chave: 'preco_desconto', valor: '15.00', descricao: 'Valor descontado por problema (R$)' },
|
||||
{ chave: 'preco_extraordinaria', valor: '25.00', descricao: 'Valor coringa para entregas fora do planejamento (R$)' },
|
||||
{ chave: 'preco_termo', valor: '15.00', descricao: 'Valor pago por entrega de termo (R$)' },
|
||||
{ chave: 'notificacao_whatsapp', valor: 'false', descricao: 'Notificações via WhatsApp (true/false)' },
|
||||
{ chave: 'notificacao_email', valor: 'false', descricao: 'Notificações via e-mail (true/false)' },
|
||||
{ chave: 'empresa_nome', valor: 'Reem Transporte', descricao: 'Nome da empresa exibido no sistema' },
|
||||
|
||||
Reference in New Issue
Block a user