160 lines
6.0 KiB
Ruby
160 lines
6.0 KiB
Ruby
# app/models/consolidacao.rb
|
|
class Consolidacao < ApplicationRecord
|
|
self.table_name = 'consolidacoes'
|
|
|
|
belongs_to :criador, class_name: 'User', foreign_key: :created_by
|
|
belongs_to :finalizador, class_name: 'User', foreign_key: :finalizado_por, optional: true
|
|
belongs_to :arquivador, class_name: 'User', foreign_key: :arquivado_por, optional: true
|
|
|
|
has_many :consolidacao_motoristas, dependent: :destroy
|
|
has_many :consolidacao_entregas, dependent: :destroy
|
|
|
|
# ── Enums ───────────────────────────────────────────────────
|
|
enum status: { rascunho: 0, finalizada: 1, arquivada: 2 }
|
|
|
|
# ── Soft delete ─────────────────────────────────────────────
|
|
scope :ativas, -> { where(deleted_at: nil) }
|
|
scope :arquivadas, -> { where.not(deleted_at: nil) }
|
|
|
|
# ── Validações ──────────────────────────────────────────────
|
|
validates :nome, presence: true
|
|
validates :data_inicio, presence: true
|
|
validates :data_fim, presence: true
|
|
validates :created_by, presence: true
|
|
validate :periodo_valido
|
|
|
|
# ── Callbacks ───────────────────────────────────────────────
|
|
before_save :recalcular_valor_total
|
|
|
|
# ── Escopos ─────────────────────────────────────────────────
|
|
scope :recentes, -> { order(created_at: :desc) }
|
|
scope :no_periodo, ->(i, f) { where('data_inicio >= ? AND data_fim <= ?', i, f) }
|
|
|
|
# ── 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)
|
|
.having('COUNT(*) > 0 AND COUNT(*) = COUNT(pago_em)')
|
|
.select(:consolidacao_id))
|
|
}
|
|
scope :pagamento_pendente, -> {
|
|
where(id: ConsolidacaoMotorista.group(:consolidacao_id)
|
|
.having('COUNT(pago_em) = 0')
|
|
.select(:consolidacao_id))
|
|
}
|
|
scope :pagamento_parcial, -> {
|
|
where(id: ConsolidacaoMotorista.group(:consolidacao_id)
|
|
.having('COUNT(pago_em) > 0 AND COUNT(pago_em) < COUNT(*)')
|
|
.select(:consolidacao_id))
|
|
}
|
|
|
|
# ── Métodos ─────────────────────────────────────────────────
|
|
|
|
def arquivar!(user)
|
|
update!(
|
|
deleted_at: Time.current,
|
|
status: :arquivada,
|
|
arquivado_por: user&.id,
|
|
arquivado_em: Time.current
|
|
)
|
|
end
|
|
|
|
# Restaura uma consolidação arquivada. Como o status original não é guardado,
|
|
# inferimos: se já tinha sido finalizada (finalizado_em presente) volta para
|
|
# :finalizada, senão para :rascunho. Limpa os campos de arquivamento.
|
|
def reativar!(_user)
|
|
update!(
|
|
deleted_at: nil,
|
|
status: finalizado_em? ? :finalizada : :rascunho,
|
|
arquivado_por: nil,
|
|
arquivado_em: nil
|
|
)
|
|
end
|
|
|
|
# ── Pagamento (status derivado dos motoristas da consolidação) ──
|
|
# :pendente (nenhum pago) · :pago (todos pagos) · :parcial (alguns).
|
|
def status_pagamento
|
|
total = consolidacao_motoristas.count
|
|
return :pendente if total.zero?
|
|
|
|
pagos = consolidacao_motoristas.pagos.count
|
|
return :pendente if pagos.zero?
|
|
|
|
pagos >= total ? :pago : :parcial
|
|
end
|
|
|
|
def valor_pago
|
|
consolidacao_motoristas.pagos.sum(:valor_total)
|
|
end
|
|
|
|
def valor_pendente
|
|
consolidacao_motoristas.pendentes.sum(:valor_total)
|
|
end
|
|
|
|
def finalizar!(user)
|
|
return false unless todas_entregas_classificadas?
|
|
|
|
update!(
|
|
status: :finalizada,
|
|
finalizado_por: user.id,
|
|
finalizado_em: Time.current
|
|
)
|
|
end
|
|
|
|
# Só está completa quando CADA motorista tem todas as suas entregas elegíveis
|
|
# 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|
|
|
total = entregas_elegiveis_count(cm.motorista_nome)
|
|
total.zero? || classificadas_count(cm.motorista_nome) >= total
|
|
end
|
|
end
|
|
|
|
def percentual_classificado
|
|
return 0 if total_entregas_estimadas.zero?
|
|
((consolidacao_entregas.count.to_f / total_entregas_estimadas) * 100).round
|
|
end
|
|
|
|
def total_entregas_estimadas
|
|
@total_entregas_estimadas ||= consolidacao_motoristas.sum do |cm|
|
|
entregas_elegiveis_count(cm.motorista_nome)
|
|
end
|
|
end
|
|
|
|
# Entregas pagas elegíveis para um motorista no período, respeitando o filtro
|
|
# de veículos da consolidação (quando houver). Fonte única usada pelo wizard,
|
|
# pela revisão e pelo gate de finalização.
|
|
def entregas_elegiveis_count(motorista)
|
|
Entrega.contar_pagas(
|
|
inicio: data_inicio,
|
|
fim: data_fim,
|
|
motorista: motorista,
|
|
vehicle: vehicle_ids.presence,
|
|
operacoes: operacoes.presence
|
|
)
|
|
end
|
|
|
|
# Rótulos amigáveis das operações vinculadas (para exibição).
|
|
def operacoes_label
|
|
Array(operacoes).map { |t| Operacao.label(t) }
|
|
end
|
|
|
|
# Entrega conta como classificada se tiver ≥1 pilar (distinct por tracking_id,
|
|
# já que agora pode haver vários pilares por entrega).
|
|
def classificadas_count(motorista)
|
|
consolidacao_entregas.where(motorista_nome: motorista).distinct.count(:tracking_id)
|
|
end
|
|
|
|
private
|
|
|
|
def periodo_valido
|
|
return unless data_inicio && data_fim
|
|
errors.add(:data_fim, 'deve ser após a data de início') if data_fim < data_inicio
|
|
end
|
|
|
|
def recalcular_valor_total
|
|
self.valor_total = consolidacao_motoristas.sum(:valor_total)
|
|
end
|
|
end
|