Adição do botão de entrega manual

This commit is contained in:
2026-06-25 11:35:41 -03:00
parent 022aac2616
commit 0c5b5bcbe6
22 changed files with 615 additions and 67 deletions

View File

@@ -33,17 +33,17 @@ class Consolidacao < ApplicationRecord
# ── Filtro por status de pagamento (derivado dos motoristas) ─
# pago_em NÃO-nulo conta como pago; COUNT(pago_em) ignora os NULL.
scope :pagamento_pago, -> {
where(id: ConsolidacaoMotorista.group(:consolidacao_id)
where(id: ConsolidacaoMotorista.ativos.group(:consolidacao_id)
.having('COUNT(*) > 0 AND COUNT(*) = COUNT(pago_em)')
.select(:consolidacao_id))
}
scope :pagamento_pendente, -> {
where(id: ConsolidacaoMotorista.group(:consolidacao_id)
where(id: ConsolidacaoMotorista.ativos.group(:consolidacao_id)
.having('COUNT(pago_em) = 0')
.select(:consolidacao_id))
}
scope :pagamento_parcial, -> {
where(id: ConsolidacaoMotorista.group(:consolidacao_id)
where(id: ConsolidacaoMotorista.ativos.group(:consolidacao_id)
.having('COUNT(pago_em) > 0 AND COUNT(pago_em) < COUNT(*)')
.select(:consolidacao_id))
}
@@ -91,21 +91,21 @@ class Consolidacao < ApplicationRecord
# ── Pagamento (status derivado dos motoristas da consolidação) ──
# :pendente (nenhum pago) · :pago (todos pagos) · :parcial (alguns).
def status_pagamento
total = consolidacao_motoristas.count
total = consolidacao_motoristas.ativos.count
return :pendente if total.zero?
pagos = consolidacao_motoristas.pagos.count
pagos = consolidacao_motoristas.ativos.pagos.count
return :pendente if pagos.zero?
pagos >= total ? :pago : :parcial
end
def valor_pago
consolidacao_motoristas.pagos.sum(:valor_total)
consolidacao_motoristas.ativos.pagos.sum(:valor_total)
end
def valor_pendente
consolidacao_motoristas.pendentes.sum(:valor_total)
consolidacao_motoristas.ativos.pendentes.sum(:valor_total)
end
def finalizar!(user)
@@ -122,7 +122,7 @@ class Consolidacao < ApplicationRecord
# classificadas. (Antes era uma soma agregada com >=, que mascarava motoristas
# pendentes quando outro tinha entregas a mais.)
def todas_entregas_classificadas?
consolidacao_motoristas.all? do |cm|
consolidacao_motoristas.ativos.all? do |cm|
total = entregas_elegiveis_count(cm.motorista_nome)
total.zero? || classificadas_count(cm.motorista_nome) >= total
end
@@ -134,7 +134,7 @@ class Consolidacao < ApplicationRecord
end
def total_entregas_estimadas
@total_entregas_estimadas ||= consolidacao_motoristas.sum do |cm|
@total_entregas_estimadas ||= consolidacao_motoristas.ativos.sum do |cm|
entregas_elegiveis_count(cm.motorista_nome)
end
end
@@ -201,6 +201,49 @@ class Consolidacao < ApplicationRecord
motorista
end
# ── Apontamento 100% manual ─────────────────────────────────
# Lança uma entrega à mão SEM consultar a base de rastreio. Caso de uso: um
# motorista foi até o local mas a NF ficou registrada com outro motorista no
# rastreio — então a busca por NF nunca o encontra, mas ele também recebe.
# O motorista vem digitado (não de uma Entrega) e é incluído se ainda não
# estiver. tracking_id é sintético (não há Entrega), e a NF/observação ficam
# guardadas em nf_manual/obs_manual.
def adicionar_apontamento_manual!(motorista:, tipos:, user:, nf: nil, obs: nil, valor_extra: nil)
tipos_validos = Array(tipos).map(&:to_s).select { |t| ConsolidacaoEntrega.tipos.key?(t) }.uniq
raise ArgumentError, 'Selecione ao menos uma classificação.' if tipos_validos.empty?
nome = motorista.to_s.strip
raise ArgumentError, 'Informe o motorista do apontamento.' if nome.blank?
tracking = "MAN-#{SecureRandom.uuid}" # sintético: agrupa os pilares como UMA entrega
transaction do
consolidacao_motoristas.find_or_create_by!(motorista_nome: nome)
tipos_validos.each do |tipo|
valor = if tipo == 'extraordinaria' && valor_extra.present?
valor_extra
else
ConsolidacaoEntrega.valor_para(tipo)
end
consolidacao_entregas.create!(
tracking_id: tracking,
tipo: tipo,
motorista_nome: nome,
valor_aplicado: valor,
manual: true,
nf_manual: nf.presence,
obs_manual: obs.presence,
created_by: user&.id
)
end
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)
@@ -211,7 +254,7 @@ class Consolidacao < ApplicationRecord
e.desconto? ? -e.valor_aplicado : e.valor_aplicado
end
cm.update_column(:valor_total, total)
update_column(:valor_total, consolidacao_motoristas.sum(:valor_total))
update_column(:valor_total, consolidacao_motoristas.ativos.sum(:valor_total))
end
private
@@ -222,6 +265,6 @@ class Consolidacao < ApplicationRecord
end
def recalcular_valor_total
self.valor_total = consolidacao_motoristas.sum(:valor_total)
self.valor_total = consolidacao_motoristas.ativos.sum(:valor_total)
end
end

View File

@@ -5,7 +5,8 @@ class ConsolidacaoMotorista < ApplicationRecord
self.table_name = 'consolidacao_motoristas'
belongs_to :consolidacao
belongs_to :pagador, class_name: 'User', foreign_key: :pago_por, optional: true
belongs_to :pagador, class_name: 'User', foreign_key: :pago_por, optional: true
belongs_to :arquivador, class_name: 'User', foreign_key: :arquivado_por, optional: true
has_many :consolidacao_entregas,
->(cm) { where(motorista_nome: cm.motorista_nome) },
foreign_key: :consolidacao_id,
@@ -20,6 +21,27 @@ class ConsolidacaoMotorista < ApplicationRecord
scope :pagos, -> { where.not(pago_em: nil) }
scope :pendentes, -> { where(pago_em: nil) }
# ── Arquivamento (reversível) ───────────────────────────────
# Um motorista arquivado sai dos totais/pagamento/gate da consolidação, mas
# os dados são mantidos e podem ser restaurados.
scope :ativos, -> { where(arquivado_em: nil) }
scope :arquivados, -> { where.not(arquivado_em: nil) }
def arquivado?
arquivado_em.present?
end
# update_columns: não dispara o before_save :recalcular_valor (que recalcula o
# valor do PRÓPRIO motorista). O total da CONSOLIDAÇÃO é recalculado pelo
# controller, que só soma os motoristas ativos.
def arquivar!(user)
update_columns(arquivado_em: Time.current, arquivado_por: user&.id, updated_at: Time.current)
end
def desarquivar!(_user)
update_columns(arquivado_em: nil, arquivado_por: nil, updated_at: Time.current)
end
def pago?
pago_em.present?
end