"
layout 'mailer'
end
diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb
index 82abbb205..ad87359af 100644
--- a/app/mailers/mailer.rb
+++ b/app/mailers/mailer.rb
@@ -42,6 +42,38 @@ class Mailer < ApplicationMailer
end
end
+ def direct_message_for_receiver(direct_message)
+ @direct_message = direct_message
+ @receiver = @direct_message.receiver
+
+ with_user(@receiver) do
+ mail(to: @receiver.email, subject: t('mailers.direct_message_for_receiver.subject'))
+ end
+ end
+
+ def direct_message_for_sender(direct_message)
+ @direct_message = direct_message
+ @sender = @direct_message.sender
+
+ with_user(@sender) do
+ mail(to: @sender.email, subject: t('mailers.direct_message_for_sender.subject'))
+ end
+ end
+
+ def proposal_notification_digest(user)
+ @notifications = user.notifications.where(notifiable_type: "ProposalNotification")
+
+ with_user(user) do
+ mail(to: user.email, subject: t('mailers.proposal_notification_digest.title', org_name: Setting['org_name']))
+ end
+ end
+
+ def user_invite(email)
+ I18n.with_locale(I18n.default_locale) do
+ mail(to: email, subject: t('mailers.user_invite.subject', org_name: Setting["org_name"]))
+ end
+ end
+
private
def with_user(user, &block)
diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb
index b86723c93..42e4ecbd3 100644
--- a/app/models/abilities/administrator.rb
+++ b/app/models/abilities/administrator.rb
@@ -46,6 +46,8 @@ module Abilities
can [:hide, :update], Budget::Investment
can :valuate, Budget::Investment, budget: { valuating: true }
can :create, Budget::ValuatorAssignment
+
+ can [:search, :edit, :update, :create, :index, :destroy], Banner
end
end
end
diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb
index f2c5ec720..fb03cbea2 100644
--- a/app/models/abilities/common.rb
+++ b/app/models/abilities/common.rb
@@ -44,14 +44,19 @@ module Abilities
can :vote_featured, Proposal
can :vote, SpendingProposal
can :create, SpendingProposal
+
can :create, Budget::Investment, budget: { phase: "accepting" }
can :vote, Budget::Investment, budget: { phase: "selecting" }
can :create, Budget::Ballot, budget: { phase: "balloting" }
+
+ can :create, DirectMessage
+ can :show, DirectMessage, sender_id: user.id
end
+ can [:create, :show], ProposalNotification, proposal: { author_id: user.id }
+
can :create, Annotation
can [:update, :destroy], Annotation, user_id: user.id
-
end
end
end
diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb
index 327210de8..561ac5994 100644
--- a/app/models/abilities/everyone.rb
+++ b/app/models/abilities/everyone.rb
@@ -12,6 +12,9 @@ module Abilities
can :read, Legislation
can :read, User
can [:search, :read], Annotation
+ can [:read], Budget
+ can [:read], Budget::Investment
+ can :new, DirectMessage
end
end
end
diff --git a/app/models/banner.rb b/app/models/banner.rb
new file mode 100644
index 000000000..db8f10635
--- /dev/null
+++ b/app/models/banner.rb
@@ -0,0 +1,20 @@
+class Banner < ActiveRecord::Base
+
+ acts_as_paranoid column: :hidden_at
+ include ActsAsParanoidAliases
+
+ validates :title, presence: true,
+ length: { minimum: 2 }
+ validates :description, presence: true
+ validates :target_url, presence: true
+ validates :style, presence: true
+ validates :image, presence: true
+ validates :post_started_at, presence: true
+ validates :post_ended_at, presence: true
+
+ scope :with_active, -> {where("post_started_at <= ?", Time.now).
+ where("post_ended_at >= ?", Time.now) }
+
+ scope :with_inactive,-> {where("post_started_at > ? or post_ended_at < ?", Time.now, Time.now) }
+
+end
\ No newline at end of file
diff --git a/app/models/budget.rb b/app/models/budget.rb
index dfcba3214..c79210b18 100644
--- a/app/models/budget.rb
+++ b/app/models/budget.rb
@@ -7,9 +7,11 @@ class Budget < ActiveRecord::Base
validates :phase, inclusion: { in: VALID_PHASES }
validates :currency_symbol, presence: true
- has_many :investments
- has_many :ballots
- has_many :headings
+ has_many :investments, dependent: :destroy
+ has_many :ballots, dependent: :destroy
+ has_many :groups, dependent: :destroy
+ has_many :headings, through: :groups
+ has_many :investments, through: :headings
def on_hold?
phase == "on_hold"
@@ -32,7 +34,6 @@ class Budget < ActiveRecord::Base
end
def heading_price(heading)
- return price unless heading.present?
heading_ids.include?(heading.id) ? heading.price : -1
end
end
diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb
index db4017285..848e2f22d 100644
--- a/app/models/budget/ballot.rb
+++ b/app/models/budget/ballot.rb
@@ -2,11 +2,14 @@ class Budget
class Ballot < ActiveRecord::Base
belongs_to :user
belongs_to :budget
- belongs_to :heading
has_many :lines, dependent: :destroy
has_many :investments, through: :lines
+ def add_investment(investment)
+ lines.create!(budget: budget, investment: investment, heading: investment.heading, group_id: investment.heading.group_id)
+ end
+
def total_amount_spent
investments.sum(:price).to_i
end
@@ -16,7 +19,17 @@ class Budget
end
def amount_available(heading)
- budget.heading_price(heading) - amount_spent(heading.try(:id))
+ budget.heading_price(heading) - amount_spent(heading.id)
+ end
+
+ def valid_heading?(heading)
+ group = heading.group
+ return false if group.budget_id != budget_id
+
+ line = lines.where(heading_id: group.heading_ids).first
+ return false if line.present? && line.heading_id != heading.id
+
+ true
end
def has_lines_with_no_heading?
diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb
index 887951922..01835cd90 100644
--- a/app/models/budget/ballot/line.rb
+++ b/app/models/budget/ballot/line.rb
@@ -2,11 +2,15 @@ class Budget
class Ballot
class Line < ActiveRecord::Base
belongs_to :ballot
+ belongs_to :budget
+ belongs_to :group
+ belongs_to :heading
belongs_to :investment
validate :insufficient_funds
validate :different_geozone, :if => :district_proposal?
validate :unfeasible
+ #needed? validates :ballot_id, :budget_id, :group_id, :heading_id, :investment_id, presence: true
def insufficient_funds
errors.add(:money, "") if ballot.amount_available(investment.heading) < investment.price.to_i
@@ -23,7 +27,6 @@ class Budget
def heading_proposal?
investment.heading_id.present?
end
-
end
end
end
diff --git a/app/models/budget/group.rb b/app/models/budget/group.rb
new file mode 100644
index 000000000..dd7910950
--- /dev/null
+++ b/app/models/budget/group.rb
@@ -0,0 +1,10 @@
+class Budget
+ class Group < ActiveRecord::Base
+ belongs_to :budget
+
+ has_many :headings, dependent: :destroy
+
+ validates :budget_id, presence: true
+ validates :name, presence: true
+ end
+end
\ No newline at end of file
diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb
index e79c5b7c8..830596912 100644
--- a/app/models/budget/heading.rb
+++ b/app/models/budget/heading.rb
@@ -1,11 +1,11 @@
class Budget
class Heading < ActiveRecord::Base
- belongs_to :budget
+ belongs_to :group
belongs_to :geozone
has_many :investments
- validates :budget_id, presence: true
+ validates :group_id, presence: true
validates :name, presence: true
validates :price, presence: true
end
diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb
index 43b0fde2f..bbdc0e73e 100644
--- a/app/models/budget/investment.rb
+++ b/app/models/budget/investment.rb
@@ -10,7 +10,6 @@ class Budget
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
- belongs_to :budget
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
belongs_to :heading
belongs_to :administrator
@@ -22,6 +21,7 @@ class Budget
validates :title, presence: true
validates :author, presence: true
validates :description, presence: true
+ validates :heading_id, presence: true
validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required?
validates :title, length: { in: 4 .. Budget::Investment.title_max_length }
@@ -44,16 +44,12 @@ class Budget
scope :with_supports, -> { where('cached_votes_up > 0') }
scope :by_heading, -> (heading_id) { where(heading_id: heading_id) }
- scope :by_budget, -> (budget_id) { where(budget_id: budget_id) }
scope :by_admin, -> (admin_id) { where(administrator_id: admin_id) }
scope :by_tag, -> (tag_name) { tagged_with(tag_name) }
scope :by_valuator, -> (valuator_id) { where("budget_valuator_assignments.valuator_id = ?", valuator_id).joins(:valuator_assignments) }
scope :for_render, -> { includes(heading: :geozone) }
- scope :with_heading, -> { where.not(heading_id: nil) }
- scope :no_heading, -> { where(heading_id: nil) }
-
before_save :calculate_confidence_score
before_validation :set_responsible_name
@@ -62,8 +58,7 @@ class Budget
end
def self.scoped_filter(params, current_filter)
- budget = Budget.find!(params[:budget_id])
- results = self.by_budget(params[:budget_id])
+ results = budget.investments
if params[:max_for_no_heading].present? || params[:max_per_heading].present?
results = limit_results(results, budget, params[:max_per_heading].to_i, params[:max_for_no_heading].to_i)
end
@@ -119,6 +114,10 @@ class Budget
where(heading_id: heading == 'all' ? nil : heading.presence)
end
+ def budget
+ heading.group.budget
+ end
+
def undecided?
feasibility == "undecided"
end
@@ -140,7 +139,7 @@ class Budget
end
def code
- "B#{budget_id}I#{id}"
+ "B#{budget.id}I#{id}"
end
def reason_for_not_being_selectable_by(user)
@@ -152,7 +151,7 @@ class Budget
def reason_for_not_being_ballotable_by(user, ballot)
return permission_problem(user) if permission_problem?(user)
return :no_ballots_allowed unless budget.balloting?
- return :different_heading_assigned unless heading_id.blank? || ballot.blank? || heading_id == ballot.heading_id || ballot.heading_id.nil?
+ return :different_heading_assigned unless ballot.valid_heading?(heading)
return :not_enough_money if ballot.present? && !enough_money?(ballot)
end
diff --git a/app/models/direct_message.rb b/app/models/direct_message.rb
new file mode 100644
index 000000000..476194aea
--- /dev/null
+++ b/app/models/direct_message.rb
@@ -0,0 +1,22 @@
+class DirectMessage < ActiveRecord::Base
+ belongs_to :sender, class_name: 'User', foreign_key: 'sender_id'
+ belongs_to :receiver, class_name: 'User', foreign_key: 'receiver_id'
+
+ validates :title, presence: true
+ validates :body, presence: true
+ validates :sender, presence: true
+ validates :receiver, presence: true
+ validate :max_per_day
+
+ scope :today, lambda { where('DATE(created_at) = ?', Date.today) }
+
+ def max_per_day
+ return if errors.any?
+ max = Setting[:direct_message_max_per_day]
+
+ if sender.direct_messages_sent.today.count >= max.to_i
+ errors.add(:title, I18n.t('activerecord.errors.models.direct_message.attributes.max_per_day.invalid'))
+ end
+ end
+
+end
diff --git a/app/models/notification.rb b/app/models/notification.rb
index 1cb500ccf..9695c1b01 100644
--- a/app/models/notification.rb
+++ b/app/models/notification.rb
@@ -21,4 +21,30 @@ class Notification < ActiveRecord::Base
Notification.create!(user_id: user_id, notifiable: notifiable)
end
end
+
+ def notifiable_title
+ case notifiable.class.name
+ when "ProposalNotification"
+ notifiable.proposal.title
+ when "Comment"
+ notifiable.commentable.title
+ else
+ notifiable.title
+ end
+ end
+
+ def notifiable_action
+ case notifiable_type
+ when "ProposalNotification"
+ "proposal_notification"
+ when "Comment"
+ "replies_to"
+ else
+ "comments_on"
+ end
+ end
+
+ def linkable_resource
+ notifiable.is_a?(ProposalNotification) ? notifiable.proposal : notifiable
+ end
end
\ No newline at end of file
diff --git a/app/models/proposal.rb b/app/models/proposal.rb
index dd310dffa..b6a8ddb26 100644
--- a/app/models/proposal.rb
+++ b/app/models/proposal.rb
@@ -16,6 +16,7 @@ class Proposal < ActiveRecord::Base
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
belongs_to :geozone
has_many :comments, as: :commentable
+ has_many :proposal_notifications
validates :title, presence: true
validates :question, presence: true
@@ -93,6 +94,10 @@ class Proposal < ActiveRecord::Base
cached_votes_up + physical_votes
end
+ def voters
+ votes_for.voters
+ end
+
def editable?
total_votes <= Setting["max_votes_for_proposal_edit"].to_i
end
@@ -146,6 +151,10 @@ class Proposal < ActiveRecord::Base
Setting['votes_for_proposal_success'].to_i
end
+ def notifications
+ proposal_notifications
+ end
+
protected
def set_responsible_name
diff --git a/app/models/proposal_notification.rb b/app/models/proposal_notification.rb
new file mode 100644
index 000000000..f73264318
--- /dev/null
+++ b/app/models/proposal_notification.rb
@@ -0,0 +1,17 @@
+class ProposalNotification < ActiveRecord::Base
+ belongs_to :author, class_name: 'User', foreign_key: 'author_id'
+ belongs_to :proposal
+
+ validates :title, presence: true
+ validates :body, presence: true
+ validates :proposal, presence: true
+ validate :minimum_interval
+
+ def minimum_interval
+ return true if proposal.try(:notifications).blank?
+ if proposal.notifications.last.created_at > (Time.now - Setting[:proposal_notification_minimum_interval_in_days].to_i.days).to_datetime
+ errors.add(:title, I18n.t('activerecord.errors.models.proposal_notification.attributes.minimum_interval.invalid', interval: Setting[:proposal_notification_minimum_interval_in_days]))
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/app/models/setting.rb b/app/models/setting.rb
index 40659ed74..9010abba5 100644
--- a/app/models/setting.rb
+++ b/app/models/setting.rb
@@ -2,6 +2,20 @@ class Setting < ActiveRecord::Base
validates :key, presence: true, uniqueness: true
default_scope { order(id: :asc) }
+ scope :banner_style, -> { where("key ilike ?", "banner-style.%")}
+ scope :banner_img, -> { where("key ilike ?", "banner-img.%")}
+
+ def type
+ if feature_flag?
+ 'feature'
+ elsif banner_style?
+ 'banner-style'
+ elsif banner_img?
+ 'banner-img'
+ else
+ 'common'
+ end
+ end
def feature_flag?
key.start_with?('feature.')
@@ -11,6 +25,14 @@ class Setting < ActiveRecord::Base
feature_flag? && value.present?
end
+ def banner_style?
+ key.start_with?('banner-style.')
+ end
+
+ def banner_img?
+ key.start_with?('banner-img.')
+ end
+
class << self
def [](key)
where(key: key).pluck(:value).first.presence
diff --git a/app/models/user.rb b/app/models/user.rb
index 8da62dd0d..bf252a6fc 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -23,6 +23,8 @@ class User < ActiveRecord::Base
has_many :spending_proposals, foreign_key: :author_id
has_many :failed_census_calls
has_many :notifications
+ has_many :direct_messages_sent, class_name: 'DirectMessage', foreign_key: :sender_id
+ has_many :direct_messages_received, class_name: 'DirectMessage', foreign_key: :receiver_id
belongs_to :geozone
validates :username, presence: true, if: :username_required?
@@ -50,6 +52,7 @@ class User < ActiveRecord::Base
scope :officials, -> { where("official_level > 0") }
scope :for_render, -> { includes(:organization) }
scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) }
+ scope :email_digest, -> { where(email_digest: true) }
before_validation :clean_document_number
@@ -135,6 +138,16 @@ class User < ActiveRecord::Base
update official_position: nil, official_level: 0
end
+ def has_official_email?
+ domain = Setting['email_domain_for_officials']
+ !email.blank? && ( (email.end_with? "@#{domain}") || (email.end_with? ".#{domain}") )
+ end
+
+ def display_official_position_badge?
+ return true if official_level > 1
+ official_position_badge? && official_level == 1
+ end
+
def block
debates_ids = Debate.where(author_id: id).pluck(:id)
comments_ids = Comment.where(user_id: id).pluck(:id)
@@ -199,11 +212,6 @@ class User < ActiveRecord::Base
!erased?
end
- def has_official_email?
- domain = Setting['email_domain_for_officials']
- !email.blank? && ( (email.end_with? "@#{domain}") || (email.end_with? ".#{domain}") )
- end
-
def locale
self[:locale] ||= I18n.default_locale.to_s
end
diff --git a/app/views/account/show.html.erb b/app/views/account/show.html.erb
index f0d2b6abc..247aa6024 100644
--- a/app/views/account/show.html.erb
+++ b/app/views/account/show.html.erb
@@ -34,7 +34,9 @@
<%= f.label :public_activity do %>
<%= f.check_box :public_activity, title: t('account.show.public_activity_label'), label: false %>
- <%= t("account.show.public_activity_label") %>
+
+ <%= t("account.show.public_activity_label") %>
+
<% end %>
@@ -43,24 +45,61 @@
<%= f.label :email_on_comment do %>
<%= f.check_box :email_on_comment, title: t('account.show.email_on_comment_label'), label: false %>
- <%= t("account.show.email_on_comment_label") %>
+
+ <%= t("account.show.email_on_comment_label") %>
+
<% end %>
<%= f.label :email_on_comment_reply do %>
<%= f.check_box :email_on_comment_reply, title: t('account.show.email_on_comment_reply_label'), label: false %>
- <%= t("account.show.email_on_comment_reply_label") %>
+
+ <%= t("account.show.email_on_comment_reply_label") %>
+
<% end %>
<%= f.label :email_newsletter_subscribed do %>
<%= f.check_box :newsletter, title: t('account.show.subscription_to_website_newsletter_label'), label: false %>
- <%= t("account.show.subscription_to_website_newsletter_label") %>
+
+ <%= t("account.show.subscription_to_website_newsletter_label") %>
+
<% end %>
+
+ <%= f.label :email_digest do %>
+ <%= f.check_box :email_digest, title: t('account.show.email_digest_label'), label: false %>
+
+ <%= t("account.show.email_digest_label") %>
+
+ <% end %>
+
+
+
+ <%= f.label :email_on_direct_message do %>
+ <%= f.check_box :email_on_direct_message, title: t('account.show.email_on_direct_message_label'), label: false %>
+
+ <%= t("account.show.email_on_direct_message_label") %>
+
+ <% end %>
+
+
+ <% if @account.official_level == 1 %>
+
+ <%= f.label :official_position_badge do %>
+ <%= f.check_box :official_position_badge,
+ title: t('account.show.official_position_badge_label'),
+ label: false %>
+
+ <%= t("account.show.official_position_badge_label") %>
+
+ <% end %>
+
+ <% end %>
+
<%= f.submit t("account.show.save_changes_submit"), class: "button" %>
diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb
index bf0382dbd..7ea48b2c6 100644
--- a/app/views/admin/_menu.html.erb
+++ b/app/views/admin/_menu.html.erb
@@ -1,115 +1,103 @@