AyuntamientoMadrid/participacion

View on GitHub
app/models/budget.rb

Summary

Maintainability
B
5 hrs
Test Coverage
class Budget < ApplicationRecord
  include Measurable
  include Sluggable
  include Reportable
  include Imageable

  translates :name, :main_link_text, :main_link_url, touch: true
  include Globalizable

  class Translation
    validate :name_uniqueness_by_budget

    def name_uniqueness_by_budget
      if Budget.joins(:translations)
               .where(name: name)
               .where.not("budget_translations.budget_id": budget_id).any?
        errors.add(:name, I18n.t("errors.messages.taken"))
      end
    end
  end

  CURRENCY_SYMBOLS = %w[€ $ £ ¥].freeze
  VOTING_STYLES = %w[knapsack approval].freeze

  validates_translation :name, presence: true
  validates_translation :main_link_url, presence: true, unless: -> { main_link_text.blank? }
  validates :phase, inclusion: { in: ->(*) { Budget::Phase::PHASE_KINDS }}
  validates :currency_symbol, presence: true
  validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
  validates :voting_style, inclusion: { in: ->(*) { VOTING_STYLES }}

  has_many :investments, dependent: :destroy
  has_many :ballots, dependent: :destroy
  has_many :groups, dependent: :destroy
  has_many :headings, through: :groups
  has_many :geozones, through: :headings
  has_many :lines, through: :ballots, class_name: "Budget::Ballot::Line"
  has_many :phases, class_name: "Budget::Phase"
  has_many :budget_administrators, dependent: :destroy
  has_many :administrators, through: :budget_administrators
  has_many :budget_valuators, dependent: :destroy
  has_many :valuators, through: :budget_valuators

  has_one :poll

  after_create :generate_phases
  accepts_nested_attributes_for :phases

  scope :published, -> { where(published: true) }
  scope :drafting,  -> { excluding(published) }
  scope :informing, -> { where(phase: "informing") }
  scope :accepting, -> { where(phase: "accepting") }
  scope :reviewing, -> { where(phase: "reviewing") }
  scope :selecting, -> { where(phase: "selecting") }
  scope :valuating, -> { where(phase: "valuating") }
  scope :valuating_or_later, -> { where(phase: Budget::Phase.kind_or_later("valuating")) }
  scope :publishing_prices, -> { where(phase: "publishing_prices") }
  scope :balloting, -> { where(phase: "balloting") }
  scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") }
  scope :finished, -> { where(phase: "finished") }
  scope :public_for_api, -> { published }

  class << self; undef :open; end
  scope :open, -> { where.not(phase: "finished") }

  def self.current
    published.order(:created_at).last
  end

  def current_phase
    phases.send(phase)
  end

  def published_phases
    phases.published.order(:id)
  end

  def starts_at
    phases.published.first&.starts_at
  end

  def ends_at
    phases.published.last&.ends_at
  end

  def description
    description_for_phase(phase)
  end

  def description_for_phase(phase)
    if phases.exists? && phases.send(phase).description.present?
      phases.send(phase).description
    else
      send("description_#{phase}")
    end
  end

  def self.title_max_length
    80
  end

  def publish!
    update!(published: true)
  end

  def drafting?
    !published?
  end

  def informing?
    phase == "informing"
  end

  def accepting?
    phase == "accepting"
  end

  def reviewing?
    phase == "reviewing"
  end

  def selecting?
    phase == "selecting"
  end

  def valuating?
    phase == "valuating"
  end

  def publishing_prices?
    phase == "publishing_prices"
  end

  def balloting?
    phase == "balloting"
  end

  def reviewing_ballots?
    phase == "reviewing_ballots"
  end

  def finished?
    phase == "finished"
  end

  def published_prices?
    Budget::Phase::PUBLISHED_PRICES_PHASES.include?(phase)
  end

  def valuating_or_later?
    current_phase&.valuating_or_later?
  end

  def publishing_prices_or_later?
    current_phase&.publishing_prices_or_later?
  end

  def balloting_or_later?
    current_phase&.balloting_or_later?
  end

  def balloting_finished?
    balloting_or_later? && !balloting?
  end

  def single_group?
    groups.one?
  end

  def single_heading?
    single_group? && headings.one?
  end

  def heading_price(heading)
    heading_ids.include?(heading.id) ? heading.price : -1
  end

  def formatted_amount(amount)
    ActionController::Base.helpers.number_to_currency(amount,
                                                      precision: 0,
                                                      locale: I18n.locale,
                                                      unit: currency_symbol)
  end

  def formatted_heading_price(heading)
    formatted_amount(heading_price(heading))
  end

  def investments_orders
    case phase
    when "accepting", "reviewing", "finished"
      %w[random]
    when "publishing_prices", "balloting", "reviewing_ballots"
      hide_money? ? %w[random] : %w[random price]
    else
      %w[random confidence_score]
    end
  end

  def investments_filters
    [
      ("winners" if finished?),
      ("selected" if publishing_prices_or_later? && !finished?),
      ("unselected" if publishing_prices_or_later?),
      ("not_unfeasible" if valuating?),
      ("unfeasible" if valuating_or_later?)
    ].compact
  end

  def email_selected
    investments.selected.order(:id).each do |investment|
      Mailer.budget_investment_selected(investment).deliver_later
    end
  end

  def email_unselected
    investments.unselected.order(:id).each do |investment|
      Mailer.budget_investment_unselected(investment).deliver_later
    end
  end

  def has_winning_investments?
    investments.winners.any?
  end

  def investments_milestone_tags
    investments.winners.map(&:milestone_tag_list).flatten.uniq.sort
  end

  def approval_voting?
    voting_style == "approval"
  end

  def show_money?
    !hide_money?
  end

  private

    def generate_phases
      Budget::Phase::PHASE_KINDS.each do |phase|
        Budget::Phase.create(
          budget: self,
          kind: phase,
          name: I18n.t("budgets.phase.#{phase}"),
          prev_phase: phases&.last,
          starts_at: phases&.last&.ends_at || Date.current,
          ends_at: (phases&.last&.ends_at || Date.current) + 1.month
        )
      end
    end

    def generate_slug?
      slug.nil? || drafting?
    end
end