This method was added in Rails 7.0 and makes the code slihgtly more readable. The downside is that it generates two queries instead of one, so it might generate some confusion when debugging SQL queries. Its impact on performance is probably negligible.
256 lines
6.1 KiB
Ruby
256 lines
6.1 KiB
Ruby
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") }
|
|
|
|
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
|