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,
|
bonus: Configuracao.preco_bonus,
|
||||||
desconto: Configuracao.preco_desconto,
|
desconto: Configuracao.preco_desconto,
|
||||||
extraordinaria: Configuracao.preco_extraordinaria,
|
extraordinaria: Configuracao.preco_extraordinaria,
|
||||||
termo: Configuracao.preco_termo
|
termo: Configuracao.preco_termo,
|
||||||
|
termo_especial: Configuracao.preco_termo_especial
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -267,25 +268,38 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
# POST /consolidacoes/:consolidacao_id/consolidacao_entregas/apontar_termo
|
# 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
|
# ENTREGA DE TERMO — lança lotes de termos para o motorista. Não há NF nem
|
||||||
# código de rastreio: só a quantidade, a um preço fixo configurável.
|
# código de rastreio: só as quantidades (normal e especial), cada tipo a um
|
||||||
|
# preço fixo configurável.
|
||||||
def apontar_termo
|
def apontar_termo
|
||||||
authorize @consolidacao, :update?
|
authorize @consolidacao, :update?
|
||||||
|
|
||||||
motorista = @consolidacao.adicionar_termos!(
|
qtd_normal = params[:quantidade].to_i
|
||||||
motorista: params[:motorista].to_s.strip,
|
qtd_especial = params[:quantidade_especial].to_i
|
||||||
quantidade: params[:quantidade],
|
raise ArgumentError, 'Informe ao menos um termo (normal ou especial).' if qtd_normal < 1 && qtd_especial < 1
|
||||||
vehicle: params[:vehicle], # veículo escolhido no lançamento (Q3)
|
|
||||||
user: current_user
|
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: qtd,
|
||||||
|
tipo: tipo,
|
||||||
|
vehicle: params[:vehicle], # veículo escolhido no lançamento (Q3)
|
||||||
|
user: current_user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
auditar!(:editar, @consolidacao,
|
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|
|
respond_to do |format|
|
||||||
format.json { render json: resumo_json(motorista).merge(motorista: motorista) }
|
format.json { render json: resumo_json(motorista).merge(motorista: motorista) }
|
||||||
format.html do
|
format.html do
|
||||||
redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: motorista),
|
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
|
||||||
end
|
end
|
||||||
rescue ArgumentError => e
|
rescue ArgumentError => e
|
||||||
@@ -534,8 +548,9 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
# Conta lançamentos manuais como ENTREGAS: cada termo vale `quantidade` (lote
|
# Conta lançamentos manuais como ENTREGAS: cada termo vale `quantidade` (lote
|
||||||
# numa só linha); os demais apontamentos manuais valem 1 por nota (tracking).
|
# numa só linha); os demais apontamentos manuais valem 1 por nota (tracking).
|
||||||
def contar_manuais(rel_manual)
|
def contar_manuais(rel_manual)
|
||||||
rel_manual.termo.sum(:quantidade) +
|
tipos_termo = ConsolidacaoEntrega.tipos.values_at(:termo, :termo_especial)
|
||||||
rel_manual.where.not(tipo: ConsolidacaoEntrega.tipos[:termo]).distinct.count(:tracking_id)
|
rel_manual.where(tipo: tipos_termo).sum(:quantidade) +
|
||||||
|
rel_manual.where.not(tipo: tipos_termo).distinct.count(:tracking_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
# `vehicles` (opcional, string ou array): quando presente, TODO o resumo
|
# `vehicles` (opcional, string ou array): quando presente, TODO o resumo
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
// app/javascript/controllers/apontamento_termo_controller.js
|
// app/javascript/controllers/apontamento_termo_controller.js
|
||||||
// Stimulus — Entrega de termo. Lança um lote de N termos para o motorista da tela.
|
// Stimulus — Entrega de termo. Lança lotes de termos (normal e/ou especial) para
|
||||||
// Entrega sem NF nem código de rastreio: só a quantidade (stepper − N +), a um
|
// o motorista da tela. Entrega sem NF nem código de rastreio: só as quantidades
|
||||||
// preço fixo configurável.
|
// (um stepper − N + por tipo), cada tipo a um preço fixo configurável.
|
||||||
|
|
||||||
import { Controller } from "@hotwired/stimulus"
|
import { Controller } from "@hotwired/stimulus"
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["modal", "quantidade", "erro", "veiculo"]
|
static targets = ["modal", "quantidade", "quantidadeEspecial", "erro", "veiculo"]
|
||||||
static values = { apontarTermoUrl: String, motorista: String }
|
static values = { apontarTermoUrl: String, motorista: String }
|
||||||
|
|
||||||
abrir() {
|
abrir() {
|
||||||
this.erroTarget.classList.add("hidden")
|
this.erroTarget.classList.add("hidden")
|
||||||
this.quantidadeTarget.value = "1"
|
this.quantidadeTarget.value = "1"
|
||||||
|
this.quantidadeEspecialTarget.value = "0"
|
||||||
this.modalTarget.classList.remove("hidden")
|
this.modalTarget.classList.remove("hidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,26 +25,39 @@ export default class extends Controller {
|
|||||||
if (event.target === this.modalTarget) this.fechar()
|
if (event.target === this.modalTarget) this.fechar()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stepper
|
// Steppers (mínimo 0 em ambos; o envio exige ao menos 1 no total)
|
||||||
mais() {
|
mais() {
|
||||||
this.quantidadeTarget.value = String(this.qtdAtual() + 1)
|
this.quantidadeTarget.value = String(this.qtdAtual(this.quantidadeTarget) + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
menos() {
|
menos() {
|
||||||
this.quantidadeTarget.value = String(Math.max(1, this.qtdAtual() - 1))
|
this.quantidadeTarget.value = String(Math.max(0, this.qtdAtual(this.quantidadeTarget) - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
qtdAtual() {
|
maisEspecial() {
|
||||||
const n = parseInt(this.quantidadeTarget.value, 10)
|
this.quantidadeEspecialTarget.value = String(this.qtdAtual(this.quantidadeEspecialTarget) + 1)
|
||||||
return Number.isFinite(n) && n > 0 ? n : 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) {
|
async adicionar(event) {
|
||||||
event?.preventDefault()
|
event?.preventDefault()
|
||||||
|
|
||||||
const quantidade = this.qtdAtual()
|
const quantidade = this.qtdAtual(this.quantidadeTarget)
|
||||||
const motorista = this.motoristaValue.trim()
|
const quantidadeEspecial = this.qtdAtual(this.quantidadeEspecialTarget)
|
||||||
|
const motorista = this.motoristaValue.trim()
|
||||||
if (!motorista) { this.mostrarErro("Motorista não identificado."); return }
|
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
|
const btn = event.currentTarget
|
||||||
btn.disabled = true
|
btn.disabled = true
|
||||||
@@ -56,9 +70,10 @@ export default class extends Controller {
|
|||||||
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content
|
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
motorista: motorista,
|
motorista: motorista,
|
||||||
quantidade: quantidade,
|
quantidade: quantidade,
|
||||||
vehicle: this.hasVeiculoTarget ? this.veiculoTarget.value : ""
|
quantidade_especial: quantidadeEspecial,
|
||||||
|
vehicle: this.hasVeiculoTarget ? this.veiculoTarget.value : ""
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const data = await resp.json()
|
const data = await resp.json()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default class extends Controller {
|
|||||||
"consolidarBtn", "consolidarHint",
|
"consolidarBtn", "consolidarHint",
|
||||||
"contadorClassificadas", "contadorTotal", "contadorManuais", "manuaisInfo",
|
"contadorClassificadas", "contadorTotal", "contadorManuais", "manuaisInfo",
|
||||||
"qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "qtdExtraordinaria",
|
"qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "qtdExtraordinaria",
|
||||||
"qtdTermo", "qtdManuais", "valorTotal"
|
"qtdTermo", "qtdTermoEspecial", "qtdManuais", "valorTotal"
|
||||||
]
|
]
|
||||||
|
|
||||||
static values = {
|
static values = {
|
||||||
@@ -187,6 +187,8 @@ export default class extends Controller {
|
|||||||
this.qtdExtraordinariaTarget.textContent = porTipo["extraordinaria"] || 0
|
this.qtdExtraordinariaTarget.textContent = porTipo["extraordinaria"] || 0
|
||||||
if (this.hasQtdTermoTarget)
|
if (this.hasQtdTermoTarget)
|
||||||
this.qtdTermoTarget.textContent = porTipo["termo"] || 0
|
this.qtdTermoTarget.textContent = porTipo["termo"] || 0
|
||||||
|
if (this.hasQtdTermoEspecialTarget)
|
||||||
|
this.qtdTermoEspecialTarget.textContent = porTipo["termo_especial"] || 0
|
||||||
|
|
||||||
const valor = dados.valor_total || 0
|
const valor = dados.valor_total || 0
|
||||||
this.valorTotalTarget.textContent =
|
this.valorTotalTarget.textContent =
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class Configuracao < ApplicationRecord
|
|||||||
preco_desconto
|
preco_desconto
|
||||||
preco_extraordinaria
|
preco_extraordinaria
|
||||||
preco_termo
|
preco_termo
|
||||||
|
preco_termo_especial
|
||||||
notificacao_whatsapp
|
notificacao_whatsapp
|
||||||
notificacao_email
|
notificacao_email
|
||||||
empresa_nome
|
empresa_nome
|
||||||
@@ -23,6 +24,7 @@ class Configuracao < ApplicationRecord
|
|||||||
preco_desconto
|
preco_desconto
|
||||||
preco_extraordinaria
|
preco_extraordinaria
|
||||||
preco_termo
|
preco_termo
|
||||||
|
preco_termo_especial
|
||||||
].freeze
|
].freeze
|
||||||
|
|
||||||
validates :chave, presence: true, inclusion: { in: CHAVES }, uniqueness: true
|
validates :chave, presence: true, inclusion: { in: CHAVES }, uniqueness: true
|
||||||
@@ -58,6 +60,10 @@ class Configuracao < ApplicationRecord
|
|||||||
valor('preco_termo').to_f
|
valor('preco_termo').to_f
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.preco_termo_especial
|
||||||
|
valor('preco_termo_especial').to_f
|
||||||
|
end
|
||||||
|
|
||||||
# Mapa usado pelo DashboardController (Fase 3)
|
# Mapa usado pelo DashboardController (Fase 3)
|
||||||
def self.mapa_de_precos
|
def self.mapa_de_precos
|
||||||
{
|
{
|
||||||
@@ -66,7 +72,8 @@ class Configuracao < ApplicationRecord
|
|||||||
bonus: preco_bonus,
|
bonus: preco_bonus,
|
||||||
desconto: preco_desconto,
|
desconto: preco_desconto,
|
||||||
extraordinaria: preco_extraordinaria,
|
extraordinaria: preco_extraordinaria,
|
||||||
termo: preco_termo
|
termo: preco_termo,
|
||||||
|
termo_especial: preco_termo_especial
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -77,6 +84,7 @@ class Configuracao < ApplicationRecord
|
|||||||
'preco_desconto' => 'Desconto',
|
'preco_desconto' => 'Desconto',
|
||||||
'preco_extraordinaria' => 'Entrega Extraordinária',
|
'preco_extraordinaria' => 'Entrega Extraordinária',
|
||||||
'preco_termo' => 'Entrega de Termo',
|
'preco_termo' => 'Entrega de Termo',
|
||||||
|
'preco_termo_especial' => 'Entrega de Termo Especial',
|
||||||
'notificacao_whatsapp' => 'Notificação WhatsApp',
|
'notificacao_whatsapp' => 'Notificação WhatsApp',
|
||||||
'notificacao_email' => 'Notificação E-mail',
|
'notificacao_email' => 'Notificação E-mail',
|
||||||
'empresa_nome' => 'Nome da Empresa'
|
'empresa_nome' => 'Nome da Empresa'
|
||||||
@@ -89,6 +97,7 @@ class Configuracao < ApplicationRecord
|
|||||||
'preco_desconto' => '⚠️',
|
'preco_desconto' => '⚠️',
|
||||||
'preco_extraordinaria' => '✨',
|
'preco_extraordinaria' => '✨',
|
||||||
'preco_termo' => '📄',
|
'preco_termo' => '📄',
|
||||||
|
'preco_termo_especial' => '📋',
|
||||||
'notificacao_whatsapp' => '💬',
|
'notificacao_whatsapp' => '💬',
|
||||||
'notificacao_email' => '✉️',
|
'notificacao_email' => '✉️',
|
||||||
'empresa_nome' => '🏢'
|
'empresa_nome' => '🏢'
|
||||||
|
|||||||
@@ -339,10 +339,15 @@ class Consolidacao < ApplicationRecord
|
|||||||
# para atrelar (só motorista + quantidade, a um preço fixo configurável). Fica
|
# 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
|
# 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.
|
# 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
|
nome = motorista.to_s.strip
|
||||||
raise ArgumentError, 'Informe o motorista do termo.' if nome.blank?
|
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
|
qtd = quantidade.to_i
|
||||||
raise ArgumentError, 'Informe uma quantidade válida de termos.' if qtd < 1
|
raise ArgumentError, 'Informe uma quantidade válida de termos.' if qtd < 1
|
||||||
|
|
||||||
@@ -351,10 +356,10 @@ class Consolidacao < ApplicationRecord
|
|||||||
|
|
||||||
consolidacao_entregas.create!(
|
consolidacao_entregas.create!(
|
||||||
tracking_id: "TERMO-#{SecureRandom.uuid}", # sintético: não há Entrega/NF
|
tracking_id: "TERMO-#{SecureRandom.uuid}", # sintético: não há Entrega/NF
|
||||||
tipo: 'termo',
|
tipo: tipo,
|
||||||
motorista_nome: nome,
|
motorista_nome: nome,
|
||||||
quantidade: qtd,
|
quantidade: qtd,
|
||||||
valor_aplicado: qtd * ConsolidacaoEntrega.valor_para('termo'), # total do lote
|
valor_aplicado: qtd * ConsolidacaoEntrega.valor_para(tipo), # total do lote
|
||||||
manual: true,
|
manual: true,
|
||||||
vehicle: vehicle.presence, # veículo escolhido no lançamento (Q3)
|
vehicle: vehicle.presence, # veículo escolhido no lançamento (Q3)
|
||||||
created_by: user&.id
|
created_by: user&.id
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class ConsolidacaoEntrega < ApplicationRecord
|
|||||||
bonus: 2,
|
bonus: 2,
|
||||||
desconto: 3,
|
desconto: 3,
|
||||||
extraordinaria: 4,
|
extraordinaria: 4,
|
||||||
termo: 5
|
termo: 5,
|
||||||
|
termo_especial: 6
|
||||||
}
|
}
|
||||||
|
|
||||||
TIPO_CORES = {
|
TIPO_CORES = {
|
||||||
@@ -24,7 +25,8 @@ class ConsolidacaoEntrega < ApplicationRecord
|
|||||||
'bonus' => { bg: 'bg-white border border-orange-500', text: 'text-black', label: 'Bônus' },
|
'bonus' => { bg: 'bg-white border border-orange-500', text: 'text-black', label: 'Bônus' },
|
||||||
'desconto' => { bg: 'bg-gray-900', text: 'text-white', label: 'Desconto' },
|
'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' }
|
'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
|
}.freeze
|
||||||
|
|
||||||
# Uma entrega pode ter vários pilares (Normal + Bônus + Retirada…),
|
# 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 'desconto' then Configuracao.preco_desconto
|
||||||
when 'extraordinaria' then Configuracao.preco_extraordinaria
|
when 'extraordinaria' then Configuracao.preco_extraordinaria
|
||||||
when 'termo' then Configuracao.preco_termo
|
when 'termo' then Configuracao.preco_termo
|
||||||
|
when 'termo_especial' then Configuracao.preco_termo_especial
|
||||||
else 0
|
else 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ module Pdf
|
|||||||
original&.vehicle.presence || (e.manual? ? 'manual' : '—'),
|
original&.vehicle.presence || (e.manual? ? 'manual' : '—'),
|
||||||
original ? "NF #{original.numero_nf}" : (e.nf_manual.present? ? "NF #{e.nf_manual}" : '—'),
|
original ? "NF #{original.numero_nf}" : (e.nf_manual.present? ? "NF #{e.nf_manual}" : '—'),
|
||||||
original ? original.address.to_s : (e.obs_manual.presence || '—'),
|
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)
|
(e.desconto? ? '-' : '') + moeda(e.valor_aplicado)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,14 +12,15 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Resumo por tipo %>
|
<%# 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'],
|
['entrega_normal', 'Normal', 'bg-orange-500 text-black'],
|
||||||
['retirada', 'Retirada', 'bg-orange-800 text-white'],
|
['retirada', 'Retirada', 'bg-orange-800 text-white'],
|
||||||
['bonus', 'Bônus', 'bg-white text-black'],
|
['bonus', 'Bônus', 'bg-white text-black'],
|
||||||
['desconto', 'Desconto', 'bg-black text-white border border-gray-700'],
|
['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']
|
['termo', 'Termo', 'bg-blue-600 text-white'],
|
||||||
|
['termo_especial', 'Termo Especial', 'bg-cyan-600 text-white']
|
||||||
].each do |tipo, label, cores| %>
|
].each do |tipo, label, cores| %>
|
||||||
<div class="<%= cores %> rounded-xl p-4 text-center">
|
<div class="<%= cores %> rounded-xl p-4 text-center">
|
||||||
<p class="font-black text-3xl"><%= @resumo[tipo] || 0 %></p>
|
<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 text-gray-300 text-xs"><%= c.entrega_original&.address.presence || endereco_manual.presence || '—' %></td>
|
||||||
<td class="px-4 py-3">
|
<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">
|
<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>
|
</span>
|
||||||
<% if c.manual? %>
|
<% 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>
|
<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,20 +288,14 @@
|
|||||||
<button type="button" data-action="apontamento-termo#fechar"
|
<button type="button" data-action="apontamento-termo#fechar"
|
||||||
class="text-gray-500 hover:text-white text-2xl leading-none">✕</button>
|
class="text-gray-500 hover:text-white text-2xl leading-none">✕</button>
|
||||||
</div>
|
</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>
|
<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 %>
|
<%# Motorista (contexto da tela) %>
|
||||||
<div class="flex items-center justify-between bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg px-4 py-3 mb-4">
|
<div class="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-gray-500 text-xs">Motorista</p>
|
<p class="text-white font-semibold">🚚 <%= @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>
|
</div>
|
||||||
|
|
||||||
<%# Veículo (Q3) — a qual carro estes termos pertencem %>
|
<%# Veículo (Q3) — a qual carro estes termos pertencem %>
|
||||||
@@ -314,15 +308,32 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<%# Stepper de quantidade %>
|
<%# Steppers de quantidade — termo normal e termo especial %>
|
||||||
<label class="block text-gray-400 text-sm mb-2">Quantidade de termos</label>
|
<div class="grid grid-cols-2 gap-3 mb-5">
|
||||||
<div class="flex items-center justify-center gap-3 mb-5">
|
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3">
|
||||||
<button type="button" data-action="apontamento-termo#menos"
|
<p class="text-white text-sm font-semibold text-center">📄 Termo Normal</p>
|
||||||
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>
|
<p class="text-blue-400 text-xs text-center mb-2"><%= moeda(@precos[:termo]) %> cada</p>
|
||||||
<input data-apontamento-termo-target="quantidade" type="text" inputmode="numeric" value="1" readonly
|
<div class="flex items-center justify-center gap-2">
|
||||||
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#menos"
|
||||||
<button type="button" data-action="apontamento-termo#mais"
|
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>
|
||||||
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="text" inputmode="numeric" value="1" 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#mais"
|
||||||
|
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>
|
</div>
|
||||||
|
|
||||||
<button type="button" data-action="apontamento-termo#adicionar"
|
<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="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>
|
<span class="text-white font-bold" data-validacao-target="qtdTermo">0</span>
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<div class="border-t border-[#2a2a2a] pt-4 flex justify-between items-center text-sm">
|
<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>⚠️ Desconto: -<%= moeda(@precos[:desconto]) %></p>
|
||||||
<p>✨ Extraordinária: <%= moeda(@precos[:extraordinaria]) %> (valor por entrega)</p>
|
<p>✨ Extraordinária: <%= moeda(@precos[:extraordinaria]) %> (valor por entrega)</p>
|
||||||
<p>📄 Termo: <%= moeda(@precos[:termo]) %></p>
|
<p>📄 Termo: <%= moeda(@precos[:termo]) %></p>
|
||||||
|
<p>📋 Termo Especial: <%= moeda(@precos[:termo_especial]) %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -171,14 +171,20 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-[#2a2a2a]">
|
<tbody class="divide-y divide-[#2a2a2a]">
|
||||||
<% @apontamentos.each do |(motorista, _tracking), pilares| %>
|
<% @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 %>
|
<% entrega = eh_termo ? nil : pilares.first.entrega_original %>
|
||||||
<% nf_exibida = entrega&.numero_nf || pilares.first.nf_manual %>
|
<% nf_exibida = entrega&.numero_nf || pilares.first.nf_manual %>
|
||||||
<% obs_manual = pilares.first.obs_manual %>
|
<% obs_manual = pilares.first.obs_manual %>
|
||||||
<% total = pilares.sum { |p| p.desconto? ? -p.valor_aplicado : p.valor_aplicado } %>
|
<% total = pilares.sum { |p| p.desconto? ? -p.valor_aplicado : p.valor_aplicado } %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-3">
|
<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">
|
<p class="text-gray-400 text-xs">
|
||||||
<%= motorista %><% if entrega&.vehicle.present? %> · <%= entrega.vehicle %><% end %>
|
<%= motorista %><% if entrega&.vehicle.present? %> · <%= entrega.vehicle %><% end %>
|
||||||
<% if entrega.nil? && obs_manual.present? %> · <%= obs_manual %><% end %>
|
<% if entrega.nil? && obs_manual.present? %> · <%= obs_manual %><% end %>
|
||||||
@@ -187,7 +193,7 @@
|
|||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
<% pilares.each do |p| %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</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_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_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', 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_whatsapp', valor: 'false', descricao: 'Notificações via WhatsApp (true/false)' },
|
||||||
{ chave: 'notificacao_email', valor: 'false', descricao: 'Notificações via e-mail (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' },
|
{ 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
|
it 'retorna 0.0 quando a chave não existe' do
|
||||||
expect(Configuracao.preco_bonus).to eq(0.0)
|
expect(Configuracao.preco_bonus).to eq(0.0)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe '#valor_formatado' do
|
describe '#valor_formatado' do
|
||||||
|
|||||||
Reference in New Issue
Block a user