Adição de mais um preço 'Termo especial'
This commit is contained in:
@@ -64,7 +64,8 @@ class ConsolidacaoEntregasController < ApplicationController
|
||||
bonus: Configuracao.preco_bonus,
|
||||
desconto: Configuracao.preco_desconto,
|
||||
extraordinaria: Configuracao.preco_extraordinaria,
|
||||
termo: Configuracao.preco_termo
|
||||
termo: Configuracao.preco_termo,
|
||||
termo_especial: Configuracao.preco_termo_especial
|
||||
}
|
||||
end
|
||||
|
||||
@@ -267,25 +268,38 @@ class ConsolidacaoEntregasController < ApplicationController
|
||||
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.
|
||||
# ENTREGA DE TERMO — lança lotes de termos para o motorista. Não há NF nem
|
||||
# código de rastreio: só as quantidades (normal e especial), cada tipo a um
|
||||
# preço fixo configurável.
|
||||
def apontar_termo
|
||||
authorize @consolidacao, :update?
|
||||
|
||||
qtd_normal = params[:quantidade].to_i
|
||||
qtd_especial = params[:quantidade_especial].to_i
|
||||
raise ArgumentError, 'Informe ao menos um termo (normal ou especial).' if qtd_normal < 1 && qtd_especial < 1
|
||||
|
||||
motorista = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
{ 'termo' => qtd_normal, 'termo_especial' => qtd_especial }.each do |tipo, qtd|
|
||||
next if qtd < 1
|
||||
|
||||
motorista = @consolidacao.adicionar_termos!(
|
||||
motorista: params[:motorista].to_s.strip,
|
||||
quantidade: params[:quantidade],
|
||||
quantidade: qtd,
|
||||
tipo: tipo,
|
||||
vehicle: params[:vehicle], # veículo escolhido no lançamento (Q3)
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
end
|
||||
auditar!(:editar, @consolidacao,
|
||||
dados_novos: { entrega_termo: params[:quantidade].to_i, motorista: motorista })
|
||||
dados_novos: { entrega_termo: qtd_normal, entrega_termo_especial: qtd_especial, 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}."
|
||||
notice: "#{qtd_normal + qtd_especial} entrega(s) de termo adicionada(s) para #{motorista}."
|
||||
end
|
||||
end
|
||||
rescue ArgumentError => e
|
||||
@@ -534,8 +548,9 @@ class ConsolidacaoEntregasController < ApplicationController
|
||||
# Conta lançamentos manuais como ENTREGAS: cada termo vale `quantidade` (lote
|
||||
# numa só linha); os demais apontamentos manuais valem 1 por nota (tracking).
|
||||
def contar_manuais(rel_manual)
|
||||
rel_manual.termo.sum(:quantidade) +
|
||||
rel_manual.where.not(tipo: ConsolidacaoEntrega.tipos[:termo]).distinct.count(:tracking_id)
|
||||
tipos_termo = ConsolidacaoEntrega.tipos.values_at(:termo, :termo_especial)
|
||||
rel_manual.where(tipo: tipos_termo).sum(:quantidade) +
|
||||
rel_manual.where.not(tipo: tipos_termo).distinct.count(:tracking_id)
|
||||
end
|
||||
|
||||
# `vehicles` (opcional, string ou array): quando presente, TODO o resumo
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
// 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.
|
||||
// Stimulus — Entrega de termo. Lança lotes de termos (normal e/ou especial) para
|
||||
// o motorista da tela. Entrega sem NF nem código de rastreio: só as quantidades
|
||||
// (um stepper − N + por tipo), cada tipo a um preço fixo configurável.
|
||||
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["modal", "quantidade", "erro", "veiculo"]
|
||||
static targets = ["modal", "quantidade", "quantidadeEspecial", "erro", "veiculo"]
|
||||
static values = { apontarTermoUrl: String, motorista: String }
|
||||
|
||||
abrir() {
|
||||
this.erroTarget.classList.add("hidden")
|
||||
this.quantidadeTarget.value = "1"
|
||||
this.quantidadeEspecialTarget.value = "0"
|
||||
this.modalTarget.classList.remove("hidden")
|
||||
}
|
||||
|
||||
@@ -24,26 +25,39 @@ export default class extends Controller {
|
||||
if (event.target === this.modalTarget) this.fechar()
|
||||
}
|
||||
|
||||
// Stepper
|
||||
// Steppers (mínimo 0 em ambos; o envio exige ao menos 1 no total)
|
||||
mais() {
|
||||
this.quantidadeTarget.value = String(this.qtdAtual() + 1)
|
||||
this.quantidadeTarget.value = String(this.qtdAtual(this.quantidadeTarget) + 1)
|
||||
}
|
||||
|
||||
menos() {
|
||||
this.quantidadeTarget.value = String(Math.max(1, this.qtdAtual() - 1))
|
||||
this.quantidadeTarget.value = String(Math.max(0, this.qtdAtual(this.quantidadeTarget) - 1))
|
||||
}
|
||||
|
||||
qtdAtual() {
|
||||
const n = parseInt(this.quantidadeTarget.value, 10)
|
||||
return Number.isFinite(n) && n > 0 ? n : 1
|
||||
maisEspecial() {
|
||||
this.quantidadeEspecialTarget.value = String(this.qtdAtual(this.quantidadeEspecialTarget) + 1)
|
||||
}
|
||||
|
||||
menosEspecial() {
|
||||
this.quantidadeEspecialTarget.value = String(Math.max(0, this.qtdAtual(this.quantidadeEspecialTarget) - 1))
|
||||
}
|
||||
|
||||
qtdAtual(alvo) {
|
||||
const n = parseInt(alvo.value, 10)
|
||||
return Number.isFinite(n) && n > 0 ? n : 0
|
||||
}
|
||||
|
||||
async adicionar(event) {
|
||||
event?.preventDefault()
|
||||
|
||||
const quantidade = this.qtdAtual()
|
||||
const quantidade = this.qtdAtual(this.quantidadeTarget)
|
||||
const quantidadeEspecial = this.qtdAtual(this.quantidadeEspecialTarget)
|
||||
const motorista = this.motoristaValue.trim()
|
||||
if (!motorista) { this.mostrarErro("Motorista não identificado."); return }
|
||||
if (quantidade + quantidadeEspecial < 1) {
|
||||
this.mostrarErro("Informe ao menos um termo (normal ou especial).")
|
||||
return
|
||||
}
|
||||
|
||||
const btn = event.currentTarget
|
||||
btn.disabled = true
|
||||
@@ -58,6 +72,7 @@ export default class extends Controller {
|
||||
body: JSON.stringify({
|
||||
motorista: motorista,
|
||||
quantidade: quantidade,
|
||||
quantidade_especial: quantidadeEspecial,
|
||||
vehicle: this.hasVeiculoTarget ? this.veiculoTarget.value : ""
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class extends Controller {
|
||||
"consolidarBtn", "consolidarHint",
|
||||
"contadorClassificadas", "contadorTotal", "contadorManuais", "manuaisInfo",
|
||||
"qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "qtdExtraordinaria",
|
||||
"qtdTermo", "qtdManuais", "valorTotal"
|
||||
"qtdTermo", "qtdTermoEspecial", "qtdManuais", "valorTotal"
|
||||
]
|
||||
|
||||
static values = {
|
||||
@@ -187,6 +187,8 @@ export default class extends Controller {
|
||||
this.qtdExtraordinariaTarget.textContent = porTipo["extraordinaria"] || 0
|
||||
if (this.hasQtdTermoTarget)
|
||||
this.qtdTermoTarget.textContent = porTipo["termo"] || 0
|
||||
if (this.hasQtdTermoEspecialTarget)
|
||||
this.qtdTermoEspecialTarget.textContent = porTipo["termo_especial"] || 0
|
||||
|
||||
const valor = dados.valor_total || 0
|
||||
this.valorTotalTarget.textContent =
|
||||
|
||||
@@ -10,6 +10,7 @@ class Configuracao < ApplicationRecord
|
||||
preco_desconto
|
||||
preco_extraordinaria
|
||||
preco_termo
|
||||
preco_termo_especial
|
||||
notificacao_whatsapp
|
||||
notificacao_email
|
||||
empresa_nome
|
||||
@@ -23,6 +24,7 @@ class Configuracao < ApplicationRecord
|
||||
preco_desconto
|
||||
preco_extraordinaria
|
||||
preco_termo
|
||||
preco_termo_especial
|
||||
].freeze
|
||||
|
||||
validates :chave, presence: true, inclusion: { in: CHAVES }, uniqueness: true
|
||||
@@ -58,6 +60,10 @@ class Configuracao < ApplicationRecord
|
||||
valor('preco_termo').to_f
|
||||
end
|
||||
|
||||
def self.preco_termo_especial
|
||||
valor('preco_termo_especial').to_f
|
||||
end
|
||||
|
||||
# Mapa usado pelo DashboardController (Fase 3)
|
||||
def self.mapa_de_precos
|
||||
{
|
||||
@@ -66,7 +72,8 @@ class Configuracao < ApplicationRecord
|
||||
bonus: preco_bonus,
|
||||
desconto: preco_desconto,
|
||||
extraordinaria: preco_extraordinaria,
|
||||
termo: preco_termo
|
||||
termo: preco_termo,
|
||||
termo_especial: preco_termo_especial
|
||||
}
|
||||
end
|
||||
|
||||
@@ -77,6 +84,7 @@ class Configuracao < ApplicationRecord
|
||||
'preco_desconto' => 'Desconto',
|
||||
'preco_extraordinaria' => 'Entrega Extraordinária',
|
||||
'preco_termo' => 'Entrega de Termo',
|
||||
'preco_termo_especial' => 'Entrega de Termo Especial',
|
||||
'notificacao_whatsapp' => 'Notificação WhatsApp',
|
||||
'notificacao_email' => 'Notificação E-mail',
|
||||
'empresa_nome' => 'Nome da Empresa'
|
||||
@@ -89,6 +97,7 @@ class Configuracao < ApplicationRecord
|
||||
'preco_desconto' => '⚠️',
|
||||
'preco_extraordinaria' => '✨',
|
||||
'preco_termo' => '📄',
|
||||
'preco_termo_especial' => '📋',
|
||||
'notificacao_whatsapp' => '💬',
|
||||
'notificacao_email' => '✉️',
|
||||
'empresa_nome' => '🏢'
|
||||
|
||||
@@ -339,10 +339,15 @@ class Consolidacao < ApplicationRecord
|
||||
# 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:, vehicle: nil)
|
||||
# `tipo` distingue termo normal ('termo') de termo especial ('termo_especial'),
|
||||
# cada um com seu preço configurável.
|
||||
def adicionar_termos!(motorista:, quantidade:, user:, vehicle: nil, tipo: 'termo')
|
||||
nome = motorista.to_s.strip
|
||||
raise ArgumentError, 'Informe o motorista do termo.' if nome.blank?
|
||||
|
||||
tipo = tipo.to_s
|
||||
raise ArgumentError, 'Tipo de termo inválido.' unless %w[termo termo_especial].include?(tipo)
|
||||
|
||||
qtd = quantidade.to_i
|
||||
raise ArgumentError, 'Informe uma quantidade válida de termos.' if qtd < 1
|
||||
|
||||
@@ -351,10 +356,10 @@ class Consolidacao < ApplicationRecord
|
||||
|
||||
consolidacao_entregas.create!(
|
||||
tracking_id: "TERMO-#{SecureRandom.uuid}", # sintético: não há Entrega/NF
|
||||
tipo: 'termo',
|
||||
tipo: tipo,
|
||||
motorista_nome: nome,
|
||||
quantidade: qtd,
|
||||
valor_aplicado: qtd * ConsolidacaoEntrega.valor_para('termo'), # total do lote
|
||||
valor_aplicado: qtd * ConsolidacaoEntrega.valor_para(tipo), # total do lote
|
||||
manual: true,
|
||||
vehicle: vehicle.presence, # veículo escolhido no lançamento (Q3)
|
||||
created_by: user&.id
|
||||
|
||||
@@ -15,7 +15,8 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
bonus: 2,
|
||||
desconto: 3,
|
||||
extraordinaria: 4,
|
||||
termo: 5
|
||||
termo: 5,
|
||||
termo_especial: 6
|
||||
}
|
||||
|
||||
TIPO_CORES = {
|
||||
@@ -24,7 +25,8 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
'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' },
|
||||
'termo' => { bg: 'bg-blue-600', text: 'text-white', label: 'Entrega de Termo' }
|
||||
'termo' => { bg: 'bg-blue-600', text: 'text-white', label: 'Entrega de Termo' },
|
||||
'termo_especial' => { bg: 'bg-cyan-600', text: 'text-white', label: 'Entrega de Termo Especial' }
|
||||
}.freeze
|
||||
|
||||
# Uma entrega pode ter vários pilares (Normal + Bônus + Retirada…),
|
||||
@@ -54,6 +56,7 @@ class ConsolidacaoEntrega < ApplicationRecord
|
||||
when 'desconto' then Configuracao.preco_desconto
|
||||
when 'extraordinaria' then Configuracao.preco_extraordinaria
|
||||
when 'termo' then Configuracao.preco_termo
|
||||
when 'termo_especial' then Configuracao.preco_termo_especial
|
||||
else 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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.termo? && e.quantidade > 1 ? "#{e.tipo_cor[:label]} ×#{e.quantidade}" : e.tipo_cor[:label],
|
||||
(e.termo? || e.termo_especial?) && e.quantidade > 1 ? "#{e.tipo_cor[:label]} ×#{e.quantidade}" : e.tipo_cor[:label],
|
||||
(e.desconto? ? '-' : '') + moeda(e.valor_aplicado)
|
||||
]
|
||||
end
|
||||
|
||||
@@ -12,14 +12,15 @@
|
||||
</div>
|
||||
|
||||
<%# Resumo por tipo %>
|
||||
<div class="grid grid-cols-2 md:grid-cols-6 gap-3 mb-6">
|
||||
<div class="grid grid-cols-2 md:grid-cols-7 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'],
|
||||
['termo', 'Termo', 'bg-blue-600 text-white']
|
||||
['termo', 'Termo', 'bg-blue-600 text-white'],
|
||||
['termo_especial', 'Termo Especial', 'bg-cyan-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>
|
||||
@@ -67,7 +68,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] %><% if c.termo? && c.quantidade > 1 %> ×<%= c.quantidade %><% end %>
|
||||
<%= c.tipo_cor[:label] %><% if (c.termo? || c.termo_especial?) && 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>
|
||||
|
||||
@@ -288,21 +288,15 @@
|
||||
<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 class="text-gray-400 text-sm mb-4">Entrega sem nota fiscal nem código de rastreio. Informe quantos termos de cada tipo 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>
|
||||
<%# Motorista (contexto da tela) %>
|
||||
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg px-4 py-3 mb-4">
|
||||
<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>
|
||||
|
||||
<%# Veículo (Q3) — a qual carro estes termos pertencem %>
|
||||
<label class="block text-gray-400 text-sm mb-1">Veículo <span class="text-gray-600">(opcional)</span></label>
|
||||
@@ -314,15 +308,32 @@
|
||||
<% end %>
|
||||
</select>
|
||||
|
||||
<%# 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">
|
||||
<%# Steppers de quantidade — termo normal e termo especial %>
|
||||
<div class="grid grid-cols-2 gap-3 mb-5">
|
||||
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3">
|
||||
<p class="text-white text-sm font-semibold text-center">📄 Termo Normal</p>
|
||||
<p class="text-blue-400 text-xs text-center mb-2"><%= moeda(@precos[:termo]) %> cada</p>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<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>
|
||||
class="w-10 h-10 rounded-lg bg-[#1a1a1a] border border-[#2a2a2a] hover:border-blue-500 text-white text-xl font-bold leading-none">−</button>
|
||||
<input data-apontamento-termo-target="quantidade" type="text" inputmode="numeric" 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">
|
||||
class="w-14 text-center bg-[#1a1a1a] border border-[#2a2a2a] text-white text-xl font-bold rounded-lg py-2 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>
|
||||
class="w-10 h-10 rounded-lg bg-[#1a1a1a] border border-[#2a2a2a] hover:border-blue-500 text-white text-xl font-bold leading-none">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3">
|
||||
<p class="text-white text-sm font-semibold text-center">📋 Termo Especial</p>
|
||||
<p class="text-cyan-400 text-xs text-center mb-2"><%= moeda(@precos[:termo_especial]) %> cada</p>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<button type="button" data-action="apontamento-termo#menosEspecial"
|
||||
class="w-10 h-10 rounded-lg bg-[#1a1a1a] border border-[#2a2a2a] hover:border-cyan-500 text-white text-xl font-bold leading-none">−</button>
|
||||
<input data-apontamento-termo-target="quantidadeEspecial" type="text" inputmode="numeric" value="0" readonly
|
||||
class="w-14 text-center bg-[#1a1a1a] border border-[#2a2a2a] text-white text-xl font-bold rounded-lg py-2 focus:outline-none">
|
||||
<button type="button" data-action="apontamento-termo#maisEspecial"
|
||||
class="w-10 h-10 rounded-lg bg-[#1a1a1a] border border-[#2a2a2a] hover:border-cyan-500 text-white text-xl font-bold leading-none">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" data-action="apontamento-termo#adicionar"
|
||||
@@ -437,6 +448,10 @@
|
||||
<span class="flex items-center gap-2"><span class="w-3 h-3 bg-blue-600 rounded-full"></span> Termo</span>
|
||||
<span class="text-white font-bold" data-validacao-target="qtdTermo">0</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="flex items-center gap-2"><span class="w-3 h-3 bg-cyan-600 rounded-full"></span> Termo Especial</span>
|
||||
<span class="text-white font-bold" data-validacao-target="qtdTermoEspecial">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-[#2a2a2a] pt-4 flex justify-between items-center text-sm">
|
||||
@@ -456,6 +471,7 @@
|
||||
<p>⚠️ Desconto: -<%= moeda(@precos[:desconto]) %></p>
|
||||
<p>✨ Extraordinária: <%= moeda(@precos[:extraordinaria]) %> (valor por entrega)</p>
|
||||
<p>📄 Termo: <%= moeda(@precos[:termo]) %></p>
|
||||
<p>📋 Termo Especial: <%= moeda(@precos[:termo_especial]) %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -171,14 +171,20 @@
|
||||
</thead>
|
||||
<tbody class="divide-y divide-[#2a2a2a]">
|
||||
<% @apontamentos.each do |(motorista, _tracking), pilares| %>
|
||||
<% eh_termo = pilares.first.termo? %>
|
||||
<% eh_termo = pilares.first.termo? || pilares.first.termo_especial? %>
|
||||
<% 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"><%= eh_termo ? '📄 Entrega de termo' : "NF #{nf_exibida.presence || '—'}" %></p>
|
||||
<p class="text-white font-bold"><%= if pilares.first.termo?
|
||||
'📄 Entrega de termo'
|
||||
elsif pilares.first.termo_especial?
|
||||
'📋 Entrega de termo especial'
|
||||
else
|
||||
"NF #{nf_exibida.presence || '—'}"
|
||||
end %></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 %>
|
||||
@@ -187,7 +193,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] %><% if p.termo? && p.quantidade > 1 %> ×<%= p.quantidade %><% end %></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.termo_especial?) && p.quantidade > 1 %> ×<%= p.quantidade %><% end %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Cria a configuração de preço "Entrega de Termo Especial" 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 AddPrecoTermoEspecialConfiguracao < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
return if Configuracao.exists?(chave: 'preco_termo_especial')
|
||||
|
||||
Configuracao.create!(
|
||||
chave: 'preco_termo_especial',
|
||||
valor: '0.00',
|
||||
descricao: 'Valor pago por entrega de termo especial (R$)'
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
Configuracao.where(chave: 'preco_termo_especial').delete_all
|
||||
end
|
||||
end
|
||||
@@ -30,6 +30,7 @@ configs = [
|
||||
{ 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: 'preco_termo_especial', valor: '0.00', descricao: 'Valor pago por entrega de termo especial (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' },
|
||||
|
||||
@@ -30,6 +30,11 @@ RSpec.describe Configuracao, type: :model do
|
||||
it 'retorna 0.0 quando a chave não existe' do
|
||||
expect(Configuracao.preco_bonus).to eq(0.0)
|
||||
end
|
||||
|
||||
it 'aceita a chave preco_termo_especial e converte em float' do
|
||||
create(:configuracao, chave: 'preco_termo_especial', valor: '25.00')
|
||||
expect(Configuracao.preco_termo_especial).to eq(25.0)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valor_formatado' do
|
||||
|
||||
Reference in New Issue
Block a user