diff --git a/app/controllers/consolidacao_entregas_controller.rb b/app/controllers/consolidacao_entregas_controller.rb index a0092d0..df04cc8 100644 --- a/app/controllers/consolidacao_entregas_controller.rb +++ b/app/controllers/consolidacao_entregas_controller.rb @@ -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? - motorista = @consolidacao.adicionar_termos!( - motorista: params[:motorista].to_s.strip, - quantidade: params[:quantidade], - vehicle: params[:vehicle], # veículo escolhido no lançamento (Q3) - user: current_user - ) + 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: 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 diff --git a/app/javascript/controllers/apontamento_termo_controller.js b/app/javascript/controllers/apontamento_termo_controller.js index 260bd55..5068d0e 100644 --- a/app/javascript/controllers/apontamento_termo_controller.js +++ b/app/javascript/controllers/apontamento_termo_controller.js @@ -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 motorista = this.motoristaValue.trim() + 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 @@ -56,9 +70,10 @@ export default class extends Controller { "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ - motorista: motorista, - quantidade: quantidade, - vehicle: this.hasVeiculoTarget ? this.veiculoTarget.value : "" + motorista: motorista, + quantidade: quantidade, + quantidade_especial: quantidadeEspecial, + vehicle: this.hasVeiculoTarget ? this.veiculoTarget.value : "" }) }) const data = await resp.json() diff --git a/app/javascript/controllers/validacao_controller.js b/app/javascript/controllers/validacao_controller.js index a8c3c79..e75c130 100644 --- a/app/javascript/controllers/validacao_controller.js +++ b/app/javascript/controllers/validacao_controller.js @@ -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 = diff --git a/app/models/configuracao.rb b/app/models/configuracao.rb index 56ae369..d42932a 100644 --- a/app/models/configuracao.rb +++ b/app/models/configuracao.rb @@ -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' => '🏢' diff --git a/app/models/consolidacao.rb b/app/models/consolidacao.rb index 8bb25d9..7d14d9e 100644 --- a/app/models/consolidacao.rb +++ b/app/models/consolidacao.rb @@ -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 diff --git a/app/models/consolidacao_entrega.rb b/app/models/consolidacao_entrega.rb index df93357..2ce0894 100644 --- a/app/models/consolidacao_entrega.rb +++ b/app/models/consolidacao_entrega.rb @@ -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 diff --git a/app/services/pdf/relatorio_motorista_pdf.rb b/app/services/pdf/relatorio_motorista_pdf.rb index afd5b71..bb1f08c 100644 --- a/app/services/pdf/relatorio_motorista_pdf.rb +++ b/app/services/pdf/relatorio_motorista_pdf.rb @@ -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 diff --git a/app/views/consolidacao_entregas/revisar.html.erb b/app/views/consolidacao_entregas/revisar.html.erb index fd3161a..c5dc270 100644 --- a/app/views/consolidacao_entregas/revisar.html.erb +++ b/app/views/consolidacao_entregas/revisar.html.erb @@ -12,14 +12,15 @@ <%# Resumo por tipo %> -
<%= @resumo[tipo] || 0 %>
@@ -67,7 +68,7 @@Entrega sem nota fiscal nem código de rastreio. Informe quantos termos foram entregues.
+Entrega sem nota fiscal nem código de rastreio. Informe quantos termos de cada tipo foram entregues.
- <%# Motorista (contexto da tela) + preço unitário %> -Motorista
-🚚 <%= @motorista %>
-Preço por termo
-<%= moeda(Configuracao.preco_termo) %>
-Motorista
+🚚 <%= @motorista %>
📄 Termo Normal
+<%= moeda(@precos[:termo]) %> cada
+📋 Termo Especial
+<%= moeda(@precos[:termo_especial]) %> cada
+⚠️ Desconto: -<%= moeda(@precos[:desconto]) %>
✨ Extraordinária: <%= moeda(@precos[:extraordinaria]) %> (valor por entrega)
📄 Termo: <%= moeda(@precos[:termo]) %>
+📋 Termo Especial: <%= moeda(@precos[:termo_especial]) %>
<%= eh_termo ? '📄 Entrega de termo' : "NF #{nf_exibida.presence || '—'}" %>
+<%= if pilares.first.termo? + '📄 Entrega de termo' + elsif pilares.first.termo_especial? + '📋 Entrega de termo especial' + else + "NF #{nf_exibida.presence || '—'}" + end %>
<%= motorista %><% if entrega&.vehicle.present? %> · <%= entrega.vehicle %><% end %> <% if entrega.nil? && obs_manual.present? %> · <%= obs_manual %><% end %> @@ -187,7 +193,7 @@