Merge branch 'master' into api-dev-0.18.11

This commit is contained in:
Alberto Miedes Garcés
2017-01-04 16:21:32 +01:00
63 changed files with 964 additions and 165 deletions

View File

@@ -901,16 +901,12 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar {
} }
.author-deleted, .user-deleted { .author-deleted, .user-deleted {
background-color: rgba(255,255,255,.5);
color: rgba(0,0,0,.4); color: rgba(0,0,0,.4);
font-size: rem-calc(40); display: inline-block;
left: 11px; font-size: rem-calc(32);
position: absolute; line-height: rem-calc(32);
top: 72px; height: rem-calc(32);
} vertical-align: top;
.user-deleted {
top: -4px;
} }
.user-permissions { .user-permissions {
@@ -1069,10 +1065,6 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar {
background: #C0392B; background: #C0392B;
} }
.is-deleted {
background: #E7E7E7;
}
.level-1 { .level-1 {
background: #1ABC9C; background: #1ABC9C;
} }
@@ -1601,7 +1593,10 @@ table {
} }
.comment-body { .comment-body {
margin-left: rem-calc(42);
img {
margin-right: $line-height/2;
}
.reply { .reply {
background: white; background: white;
@@ -1609,20 +1604,28 @@ table {
border-left: 0; border-left: 0;
border-right: 0; border-right: 0;
font-size: $small-font-size; font-size: $small-font-size;
margin: rem-calc(6) 0; margin: $line-height/4 0;
padding: rem-calc(6); padding: $line-height/4;
position: relative; position: relative;
a.relative, [class^="icon-arrow"] {
padding-left: $line-height/2;
}
[class^="icon-arrow"] { [class^="icon-arrow"] {
font-size: rem-calc(18); font-size: $base-font-size;
left: -20px; left: -20px;
position: absolute; position: absolute;
top: 0; top: -1px;
} }
.divider { .divider {
color: $text-light; color: $text-light;
} }
form {
margin-top: $line-height/2;
}
} }
.comment-user { .comment-user {
@@ -1650,27 +1653,19 @@ table {
} }
} }
.is-deleted {
background: #E7E7E7;
margin-left: rem-calc(42);
padding: $line-height/4 $line-height/2;
}
.comment-children { .comment-children {
border-left: 1px dashed $border; border-left: 1px dashed $border;
margin-left: rem-calc(42); display: inline-block;
padding-left: $line-height/4; margin-left: rem-calc(16);
padding-left: rem-calc(8);
@media only screen and (max-width: 40em) { width: 100%;
margin-left: rem-calc(16);
}
} }
.comment-info { .comment-info {
color: $text-medium; color: $text-medium;
display: inline-block;
font-size: $small-font-size; font-size: $small-font-size;
margin-top: $line-height/4; line-height: rem-calc(32); // Same as avatar height
vertical-align: middle;
span.user-name { span.user-name {
color: $text; color: $text;
@@ -1703,6 +1698,11 @@ table {
} }
} }
.comment-form {
display: inline-block;
width: 100%;
}
// 16. Flags // 16. Flags
// --------- // ---------

View File

@@ -325,9 +325,13 @@
clear: both; clear: both;
color: $text-medium; color: $text-medium;
font-size: $small-font-size; font-size: $small-font-size;
min-height: $line-height*2; margin-bottom: $line-height/2;
position: relative; position: relative;
span {
line-height: rem-calc(32); // Same as avatar height
}
a { a {
color: $text-medium; color: $text-medium;
} }
@@ -337,15 +341,6 @@
line-height: $line-height; line-height: $line-height;
margin: 0; margin: 0;
} }
.author-deleted {
left: 0;
top: 4px;
}
.author.deleted {
margin-left: rem-calc(48);
}
} }
.debate-description, .proposal-description { .debate-description, .proposal-description {
@@ -370,9 +365,9 @@
} }
.author-photo { .author-photo {
line-height: $line-height*2; line-height: rem-calc(32);
margin-right: rem-calc(6); margin-right: rem-calc(6);
vertical-align: middle; vertical-align: top;
width: 32px; width: 32px;
} }

View File

@@ -0,0 +1,32 @@
class Admin::SignatureSheetsController < Admin::BaseController
def index
@signature_sheets = SignatureSheet.all
end
def new
@signature_sheet = SignatureSheet.new
end
def create
@signature_sheet = SignatureSheet.new(signature_sheet_params)
@signature_sheet.author = current_user
if @signature_sheet.save
@signature_sheet.delay.verify_signatures
redirect_to [:admin, @signature_sheet], notice: I18n.t('flash.actions.create.signature_sheet')
else
render :new
end
end
def show
@signature_sheet = SignatureSheet.find(params[:id])
end
private
def signature_sheet_params
params.require(:signature_sheet).permit(:signable_type, :signable_id, :document_numbers)
end
end

View File

@@ -0,0 +1,8 @@
module SignatureSheetsHelper
def signable_options
[[t("activerecord.models.proposal", count: 1), Proposal],
[t("activerecord.models.spending_proposal", count: 1), SpendingProposal]]
end
end

View File

@@ -51,7 +51,7 @@ class Proposal < ActiveRecord::Base
scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)} scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)}
scope :retired, -> { where.not(retired_at: nil) } scope :retired, -> { where.not(retired_at: nil) }
scope :not_retired, -> { where(retired_at: nil) } scope :not_retired, -> { where(retired_at: nil) }
scope :successfull, -> { where("cached_votes_up + physical_votes >= ?", Proposal.votes_needed_for_success)} scope :successfull, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success)}
def to_param def to_param
"#{id}-#{title}".parameterize "#{id}-#{title}".parameterize
@@ -100,7 +100,7 @@ class Proposal < ActiveRecord::Base
end end
def total_votes def total_votes
cached_votes_up + physical_votes cached_votes_up
end end
def voters def voters

92
app/models/signature.rb Normal file
View File

@@ -0,0 +1,92 @@
class Signature < ActiveRecord::Base
belongs_to :signature_sheet
belongs_to :user
validates :document_number, presence: true
validates :signature_sheet, presence: true
scope :verified, -> { where(verified: true) }
scope :unverified, -> { where(verified: false) }
delegate :signable, to: :signature_sheet
before_validation :clean_document_number
def verified?
user_exists? || in_census?
end
def verify
if verified?
assign_vote
mark_as_verified
end
end
def assign_vote
if user_exists?
assign_vote_to_user
else
create_user
assign_vote_to_user
end
end
def assign_vote_to_user
set_user
signable.register_vote(user, "yes")
assign_signature_to_vote
end
def assign_signature_to_vote
vote = Vote.where(votable: signable, voter: user).first
vote.update(signature: self)
end
def user_exists?
User.where(document_number: document_number).any?
end
def create_user
user_params = {
document_number: document_number,
created_from_signature: true,
verified_at: Time.now,
erased_at: Time.now,
password: random_password,
terms_of_service: '1',
email: nil
}
User.create!(user_params)
end
def clean_document_number
return if self.document_number.blank?
self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase
end
def random_password
(0...20).map { ('a'..'z').to_a[rand(26)] }.join
end
def in_census?
response = document_types.detect do |document_type|
CensusApi.new.call(document_type, document_number).valid?
end
response.present?
end
def set_user
user = User.where(document_number: document_number).first
update(user: user)
end
def mark_as_verified
update(verified: true)
end
def document_types
%w(1 2 3 4)
end
end

View File

@@ -0,0 +1,38 @@
class SignatureSheet < ActiveRecord::Base
belongs_to :signable, polymorphic: true
belongs_to :author, class_name: 'User', foreign_key: 'author_id'
VALID_SIGNABLES = %w( Proposal SpendingProposal )
has_many :signatures
validates :author, presence: true
validates :signable_type, inclusion: {in: VALID_SIGNABLES}
validates :document_numbers, presence: true
validates :signable, presence: true
validate :signable_found
def name
"#{signable_name} #{signable_id}"
end
def signable_name
I18n.t("activerecord.models.#{signable_type.underscore}", count: 1)
end
def verify_signatures
parsed_document_numbers.each do |document_number|
signature = self.signatures.where(document_number: document_number).first_or_create
signature.verify
end
update(processed: true)
end
def parsed_document_numbers
document_numbers.split(/\r\n|\n|[,]/).collect {|d| d.gsub(/\s+/, '') }
end
def signable_found
errors.add(:signable_id, :not_found) if errors.messages[:signable].present?
end
end

View File

@@ -35,6 +35,14 @@
</li> </li>
<% end %> <% end %>
<% if feature?(:signature_sheets) %>
<li <%= "class=active" if controller_name == "signature_sheets" %>>
<%= link_to admin_signature_sheets_path do %>
<span class="icon-budget"></span><%= t("admin.menu.signature_sheets") %>
<% end %>
</li>
<% end %>
<li <%= "class=active" if controller_name == "banners" %>> <li <%= "class=active" if controller_name == "banners" %>>
<%= link_to admin_banners_path do %> <%= link_to admin_banners_path do %>
<span class="icon-eye"></span><%= t("admin.menu.banner") %> <span class="icon-eye"></span><%= t("admin.menu.banner") %>

View File

@@ -0,0 +1,31 @@
<h2 class="inline-block"><%= t("admin.signature_sheets.index.title") %></h2>
<%= link_to t("admin.signature_sheets.index.new"), new_admin_signature_sheet_path,
class: "button success float-right" %>
<% if @signature_sheets.any? %>
<table>
<tr>
<th><%= t("admin.signature_sheets.name") %></th>
<th><%= t("admin.signature_sheets.author") %></th>
<th><%= t("admin.signature_sheets.created_at") %></th>
</tr>
<% @signature_sheets.each do |signature_sheet| %>
<tr id="<%= dom_id(signature_sheet) %>" class="signature_sheet">
<td>
<%= link_to signature_sheet.name, [:admin, signature_sheet] %>
</td>
<td>
<%= signature_sheet.author.name %>
</td>
<td>
<%= l(signature_sheet.created_at, format: :short) %>
</td>
</tr>
<% end %>
</table>
<% else %>
<div class="callout primary margin-top">
<%= t("admin.signature_sheets.no_signature_sheets") %>
</div>
<% end %>

View File

@@ -0,0 +1,22 @@
<%= render 'shared/back_link' %>
<h2><%= t("admin.signature_sheets.new.title") %></h2>
<%= form_for [:admin, @signature_sheet] do |f| %>
<%= render 'shared/errors',
resource: @signature_sheet %>
<div class="small-12 medium-6 large-4">
<%= f.select :signable_type, signable_options %>
</div>
<div class="small-12 medium-6 large-4">
<%= f.text_field :signable_id %>
</div>
<%= f.label :document_numbers %>
<p class="note"><%= t("admin.signature_sheets.new.document_numbers_note") %></p>
<%= f.text_area :document_numbers, rows: "6", label: false %>
<%= f.submit(class: "button", value: t("admin.signature_sheets.new.submit")) %>
<% end %>

View File

@@ -0,0 +1,44 @@
<h2 class="inline-block"><%= @signature_sheet.name %></h2>
<div class="callout secondary float-right">
<%= t("admin.signature_sheets.show.created_at") %>
<strong><%= l(@signature_sheet.created_at, format: :short) %></strong>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<%= t("admin.signature_sheets.show.author") %>
<strong><%= @signature_sheet.author.name %></strong>
</div>
<h3 id="document_count" class="block">
<%= t("admin.signature_sheets.show.document_count") %>
<%= @signature_sheet.signatures.count %>
</h3>
<div class="callout margin-top">
<p><strong><%= t("admin.signature_sheets.show.documents") %></strong></p>
<%= simple_format @signature_sheet.document_numbers %>
</div>
<div id="verified_signatures" class="callout success">
<strong>
<%= t("admin.signature_sheets.show.verified",
count: @signature_sheet.signatures.verified.count ) %>
</strong>
</div>
<div id="unverified_signatures" class="callout alert">
<p>
<strong>
<%= t("admin.signature_sheets.show.unverified",
count: @signature_sheet.signatures.unverified.count ) %>
<%= t("admin.signature_sheets.show.unverified_error") %>
</strong>
</p>
<%= @signature_sheet.signatures.unverified.map(&:document_number).join(", ") %>
</div>
<% unless @signature_sheet.processed? %>
<div class="callout primary margin-top">
<%= t("admin.signature_sheets.show.loading") %>
</div>
<% end %>

View File

@@ -1,10 +1,9 @@
<% cache [locale_and_user_status(comment), comment, commentable_cache_key(comment.commentable), comment.author, (@comment_flags[comment.id] if @comment_flags)] do %> <% cache [locale_and_user_status(comment), comment, commentable_cache_key(comment.commentable), comment.author, (@comment_flags[comment.id] if @comment_flags)] do %>
<div class="row"> <ul id="<%= dom_id(comment) %>" class="comment no-bullet small-12">
<ul id="<%= dom_id(comment) %>" class="comment no-bullet small-12 column"> <li class="comment-body">
<% if comment.hidden? || comment.user.hidden? %> <% if comment.hidden? || comment.user.hidden? %>
<% if comment.children.size > 0 %> <% if comment.children.size > 0 %>
<div class="is-deleted"> <div class="callout secondary">
<p><%= t("comments.comment.deleted") %></p> <p><%= t("comments.comment.deleted") %></p>
</div> </div>
<% end %> <% end %>
@@ -23,82 +22,84 @@
<% end %> <% end %>
<% end %> <% end %>
<li class="comment-body"> <div class="comment-info">
<div class="comment-info">
<% if comment.as_administrator? %> <% if comment.as_administrator? %>
<span class="user-name"><%= t("comments.comment.admin") %> #<%= comment.administrator_id%></span> <span class="user-name"><%= t("comments.comment.admin") %> #<%= comment.administrator_id%></span>
<% elsif comment.as_moderator? %> <% elsif comment.as_moderator? %>
<span class="user-name"><%= t("comments.comment.moderator") %> #<%= comment.moderator_id%></span> <span class="user-name"><%= t("comments.comment.moderator") %> #<%= comment.moderator_id%></span>
<% else %>
<% if comment.user.hidden? || comment.user.erased? %>
<span class="user-name"><%= t("comments.comment.user_deleted") %></span>
<% else %> <% else %>
<span class="user-name"><%= link_to comment.user.name, user_path(comment.user) %></span>
<% if comment.user.hidden? || comment.user.erased? %> <% if comment.user.display_official_position_badge? %>
<span class="user-name"><%= t("comments.comment.user_deleted") %></span>
<% else %>
<span class="user-name"><%= link_to comment.user.name, user_path(comment.user) %></span>
<% if comment.user.display_official_position_badge? %>
&nbsp;&bull;&nbsp;
<span class="label round level-<%= comment.user.official_level %>">
<%= comment.user.official_position %>
</span>
<% end %>
<% end %>
<% if comment.user.verified_organization? %>
&nbsp;&bull;&nbsp; &nbsp;&bull;&nbsp;
<span class="label round is-association"> <span class="label round level-<%= comment.user.official_level %>">
<%= t("shared.collective") %> <%= comment.user.official_position %>
</span> </span>
<% end %> <% end %>
<% if comment.user_id == comment.commentable.author_id %> <% end %>
&nbsp;&bull;&nbsp; <% if comment.user.verified_organization? %>
<span class="label round is-author"> &nbsp;&bull;&nbsp;
<%= t("comments.comment.author") %> <span class="label round is-association">
</span> <%= t("shared.collective") %>
<% end %> </span>
<% end %>
<% if comment.user_id == comment.commentable.author_id %>
&nbsp;&bull;&nbsp;
<span class="label round is-author">
<%= t("comments.comment.author") %>
</span>
<% end %> <% end %>
&nbsp;&bull;&nbsp;<time><%= l comment.created_at.to_datetime, format: :datetime %></time> <% end %>
&nbsp;&bull;&nbsp;<span><%= l comment.created_at.to_datetime, format: :datetime %></span>
</div>
<div class="comment-user
<%= user_level_class comment %>
<%= comment_author_class comment, comment.commentable.author_id %>">
<%= simple_format text_with_links(comment.body), {}, sanitize: false %>
</div>
<div id="<%= dom_id(comment) %>_reply" class="reply">
<div id="<%= dom_id(comment) %>_votes" class="comment-votes float-right">
<%= render 'comments/votes', comment: comment %>
</div> </div>
<div class="comment-user <% if comment.children.size > 0 %>
<%= user_level_class comment %> <%= link_to "#{dom_id(comment)}", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %>
<%= comment_author_class comment, comment.commentable.author_id %>"> <span class="sr-only js-child-toggle" style="display: none;"><%= t("shared.show") %></span>
<%= simple_format text_with_links(comment.body), {}, sanitize: false %> <span class="sr-only js-child-toggle"><%= t("shared.hide") %></span>
</div> <span id="<%= dom_id(comment) %>_children_arrow" class="icon-arrow-down"></span> <%= t("comments.comment.responses", count: comment.children.size) %>
<div id="<%= dom_id(comment) %>_reply" class="reply">
<span id="<%= dom_id(comment) %>_votes" class="comment-votes float-right">
<%= render 'comments/votes', comment: comment %>
</span>
<% if comment.children.size > 0 %>
<%= link_to "", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %>
<span class="sr-only js-child-toggle" style="display: none;"><%= t("shared.show") %></span>
<span class="sr-only js-child-toggle"><%= t("shared.hide") %></span>
<span id="<%= dom_id(comment) %>_children_arrow" class="icon-arrow-down"></span> <%= t("comments.comment.responses", count: comment.children.size) %>
<% end %>
<% else %>
<%= t("comments.comment.responses", count: 0) %>
<% end %> <% end %>
<% else %>
<%= t("comments.comment.responses", count: 0) %>
<% end %>
<% if user_signed_in? %> <% if user_signed_in? %>
<span class="divider">&nbsp;|&nbsp;</span> <span class="divider">&nbsp;|&nbsp;</span>
<%= link_to(comment_link_text(comment), "", <%= link_to(comment_link_text(comment), "",
class: "js-add-comment-link", data: {'id': dom_id(comment)}) %> class: "js-add-comment-link", data: {'id': dom_id(comment)}) %>
<%= render 'comments/actions', comment: comment %> <%= render 'comments/actions', comment: comment %>
<%= render 'comments/form', {commentable: comment.commentable, parent_id: comment.id, toggeable: true} %> <%= render 'comments/form', {commentable: comment.commentable, parent_id: comment.id, toggeable: true} %>
<% end %> <% end %>
</div> </div>
</li>
<% end %> <% end %>
</li>
<li>
<ul id="<%= dom_id(comment) %>_children" class="no-bullet comment-children"> <ul id="<%= dom_id(comment) %>_children" class="no-bullet comment-children">
<% child_comments_of(comment).each do |child| %> <% child_comments_of(comment).each do |child| %>
<%= render 'comments/comment', comment: child %> <li>
<%= render 'comments/comment', comment: child %>
</li>
<% end %> <% end %>
</ul> </ul>
</ul> </li>
</div> </ul>
<% end %> <% end %>

View File

@@ -1,6 +1,6 @@
<% cache [locale_and_user_status, parent_id, commentable_cache_key(commentable)] do %> <% cache [locale_and_user_status, parent_id, commentable_cache_key(commentable)] do %>
<% css_id = parent_or_commentable_dom_id(parent_id, commentable) %> <% css_id = parent_or_commentable_dom_id(parent_id, commentable) %>
<div id="js-comment-form-<%= css_id %>" <%= "style='display:none'".html_safe if toggeable %>> <div id="js-comment-form-<%= css_id %>" <%= "style='display:none'".html_safe if toggeable %> class="comment-form">
<%= form_for Comment.new, remote: true do |f| %> <%= form_for Comment.new, remote: true do |f| %>
<%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %> <%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %>
<%= f.text_area :body, id: "comment-body-#{css_id}", maxlength: Comment.body_max_length, label: false %> <%= f.text_area :body, id: "comment-body-#{css_id}", maxlength: Comment.body_max_length, label: false %>

View File

@@ -1,6 +1,5 @@
<%= form_for(@debate) do |f| %> <%= form_for(@debate) do |f| %>
<%= render 'shared/errors', resource: @debate %> <%= render 'shared/errors', resource: @debate %>
<div class="row"> <div class="row">

View File

@@ -58,6 +58,7 @@
</a> </a>
<% end %> <% end %>
</div> </div>
</aside>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@@ -0,0 +1,5 @@
<meta name="description"
content="<%= content_for?(:meta_description) ? yield(:meta_description) : setting["meta_description"] %>" />
<meta name="keywords"
content="<%= content_for?(:meta_keywords) ? yield(:meta_keywords) : setting["meta_keywords"] %>" />

View File

@@ -4,7 +4,8 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<%=render "layouts/tracking_data"%> <%= render "layouts/tracking_data" %>
<%= render "layouts/meta_tags" %>
<title><%= content_for?(:title) ? yield(:title) : setting['org_name'] %></title> <title><%= content_for?(:title) ? yield(:title) : setting['org_name'] %></title>
<%= stylesheet_link_tag "application" %> <%= stylesheet_link_tag "application" %>
<!--[if lt IE 9]> <!--[if lt IE 9]>

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title><%= content_for?(:title) ? yield(:title) : "Gobierno abierto" %></title> <title><%= content_for?(:title) ? yield(:title) : "Gobierno abierto" %></title>
<%= render "layouts/meta_tags" %>
<%= stylesheet_link_tag "application" %> <%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application", 'data-turbolinks-track' => true %> <%= javascript_include_tag "application", 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %> <%= csrf_meta_tags %>

View File

@@ -1,9 +1,9 @@
<div class="relative"> <div class="relative">
<%= link_to t("shared.advanced_search.title"), "#", id: 'js-advanced-search-title', class: "advanced-search small" %> <%= link_to t("shared.advanced_search.title"), "#advanced_search_form", id: 'js-advanced-search-title', class: "advanced-search small" %>
</div> </div>
<div class="row advanced-search-form"> <div class="row advanced-search-form">
<%= form_tag search_path, method: :get do %> <%= form_tag search_path, id: "advanced_search_form", method: :get do %>
<div id='js-advanced-search' data-advanced-search-terms=<%= @advanced_search_terms.present? %> style="display: none"> <div id='js-advanced-search' data-advanced-search-terms=<%= @advanced_search_terms.present? %> style="display: none">
<div class="small-12 column"> <div class="small-12 column">
@@ -36,6 +36,7 @@
</label> </label>
<%= text_field_tag 'advanced_search[date_min]', <%= text_field_tag 'advanced_search[date_min]',
params[:advanced_search].try(:[], :date_min), params[:advanced_search].try(:[], :date_min),
type: "date",
placeholder: t("shared.advanced_search.date_placeholder"), placeholder: t("shared.advanced_search.date_placeholder"),
class: 'js-calendar' %> class: 'js-calendar' %>
</div> </div>
@@ -45,6 +46,7 @@
</label> </label>
<%= text_field_tag 'advanced_search[date_max]', <%= text_field_tag 'advanced_search[date_max]',
params[:advanced_search].try(:[], :date_max), params[:advanced_search].try(:[], :date_max),
type: "date",
placeholder: t("shared.advanced_search.date_placeholder"), placeholder: t("shared.advanced_search.date_placeholder"),
class: 'js-calendar' %> class: 'js-calendar' %>
</div> </div>

View File

@@ -50,7 +50,7 @@
<ul> <ul>
<li> <li>
<%= t("verification.letter.new.office", <%= t("verification.letter.new.office",
office: link_to(t("verification.letter.new.offices"), t("verification.letter.new.offices_url"), office: link_to(t("verification.letter.new.offices"), setting['verification_offices_url'],
target: "blank")).html_safe %> target: "blank")).html_safe %>
</li> </li>
<li> <li>

View File

@@ -12,7 +12,7 @@
<div data-alert class="callout success"> <div data-alert class="callout success">
<%= t("verification.letter.create.flash.success_html", <%= t("verification.letter.create.flash.success_html",
offices: link_to(t("verification.letter.create.flash.offices"), offices: link_to(t("verification.letter.create.flash.offices"),
t("verification.letter.create.flash.offices_url"), setting['verification_offices_url'],
title: t('shared.target_blank_html'), title: t('shared.target_blank_html'),
target: "_blank")).html_safe target: "_blank")).html_safe
%> %>

View File

@@ -6,7 +6,7 @@
</button> </button>
<%= t("verification.residence.new.error_verifying_census", <%= t("verification.residence.new.error_verifying_census",
offices: link_to( t("verification.residence.new.error_verifying_census_offices"), offices: link_to( t("verification.residence.new.error_verifying_census_offices"),
t("verification.residence.new.error_verifying_census_offices_url"), title: t('shared.target_blank_html'), target: "_blank")).html_safe %> setting['verification_offices_url'], title: t('shared.target_blank_html'), target: "_blank")).html_safe %>
</div> </div>
<% else %> <% else %>

View File

@@ -30,6 +30,7 @@ set :keep_releases, 5
set :local_user, ENV['USER'] set :local_user, ENV['USER']
set :delayed_job_workers, 2 set :delayed_job_workers, 2
set :delayed_job_roles, :background
set(:config_files, %w( set(:config_files, %w(
log_rotation log_rotation

View File

@@ -6,5 +6,5 @@ set :ssh_options, port: deploysecret(:ssh_port)
set :stage, :preproduction set :stage, :preproduction
set :rails_env, :preproduction set :rails_env, :preproduction
server deploysecret(:server1), user: deploysecret(:user), roles: %w(web app db importer cron) server deploysecret(:server1), user: deploysecret(:user), roles: %w(web app db importer cron background)
server deploysecret(:server2), user: deploysecret(:user), roles: %w(web app db importer) server deploysecret(:server2), user: deploysecret(:user), roles: %w(web app db importer)

View File

@@ -7,6 +7,6 @@ set :stage, :production
set :rails_env, :production set :rails_env, :production
#server deploysecret(:server1), user: deploysecret(:user), roles: %w(web app db importer) #server deploysecret(:server1), user: deploysecret(:user), roles: %w(web app db importer)
server deploysecret(:server2), user: deploysecret(:user), roles: %w(web app db importer cron) server deploysecret(:server2), user: deploysecret(:user), roles: %w(web app db importer cron background)
server deploysecret(:server3), user: deploysecret(:user), roles: %w(web app db importer) server deploysecret(:server3), user: deploysecret(:user), roles: %w(web app db importer)
server deploysecret(:server4), user: deploysecret(:user), roles: %w(web app db importer) server deploysecret(:server4), user: deploysecret(:user), roles: %w(web app db importer)

View File

@@ -95,6 +95,8 @@ search:
# - '{devise,simple_form}.*' # - '{devise,simple_form}.*'
ignore_missing: ignore_missing:
- 'unauthorized.*' - 'unauthorized.*'
- 'activerecord.models.proposal'
- 'activerecord.models.spending_proposal'
- 'activerecord.errors.models.proposal_notification.*' - 'activerecord.errors.models.proposal_notification.*'
- 'activerecord.errors.models.direct_message.*' - 'activerecord.errors.models.direct_message.*'
- 'errors.messages.blank' - 'errors.messages.blank'

View File

@@ -6,7 +6,7 @@ end
Delayed::Worker.destroy_failed_jobs = false Delayed::Worker.destroy_failed_jobs = false
Delayed::Worker.sleep_delay = 2 Delayed::Worker.sleep_delay = 2
Delayed::Worker.max_attempts = 3 Delayed::Worker.max_attempts = 3
Delayed::Worker.max_run_time = 30.seconds Delayed::Worker.max_run_time = 30.minutes
Delayed::Worker.read_ahead = 10 Delayed::Worker.read_ahead = 10
Delayed::Worker.default_queue_name = 'default' Delayed::Worker.default_queue_name = 'default'
Delayed::Worker.raise_signal_exceptions = :term Delayed::Worker.raise_signal_exceptions = :term

View File

@@ -12,8 +12,8 @@ Devise.setup do |config|
# Configure the e-mail address which will be shown in Devise::Mailer, # Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class # note that it will be overwritten if you use your own mailer class
# with default "from" parameter. # with default "from" parameter.
if Rails.env.test? if Rails.env.test? || !ActiveRecord::Base.connection.table_exists?('settings')
config.mailer_sender = "noreply@example.org" config.mailer_sender = "noreply@consul.dev"
else else
config.mailer_sender = "#{Setting['mailer_from_name']} <#{Setting['mailer_from_address']}>" config.mailer_sender = "#{Setting['mailer_from_name']} <#{Setting['mailer_from_address']}>"
end end

View File

@@ -1,4 +1,6 @@
ActsAsVotable::Vote.class_eval do ActsAsVotable::Vote.class_eval do
belongs_to :signature
def self.for_debates(debates) def self.for_debates(debates)
where(votable_type: 'Debate', votable_id: debates) where(votable_type: 'Debate', votable_id: debates)
end end
@@ -14,4 +16,5 @@ ActsAsVotable::Vote.class_eval do
def value def value
vote_flag vote_flag
end end
end end

View File

@@ -69,6 +69,10 @@ en:
external_url: "Link to additional documentation" external_url: "Link to additional documentation"
geozone_id: "Scope of operation" geozone_id: "Scope of operation"
title: "Title" title: "Title"
signature_sheet:
signable_type: "Signable type"
signable_id: "Signable ID"
document_numbers: "Documents numbers"
errors: errors:
models: models:
user: user:
@@ -90,4 +94,9 @@ en:
proposal_notification: proposal_notification:
attributes: attributes:
minimum_interval: minimum_interval:
invalid: "You have to wait a minium of %{interval} days between notifications" invalid: "You have to wait a minium of %{interval} days between notifications"
signature:
attributes:
document_number:
not_in_census: 'Not verified by Census'
already_voted: 'Already voted this proposal'

View File

@@ -69,6 +69,10 @@ es:
external_url: "Enlace a documentación adicional" external_url: "Enlace a documentación adicional"
geozone_id: "Ámbito de actuación" geozone_id: "Ámbito de actuación"
title: "Título" title: "Título"
signature_sheet:
signable_type: "Tipo de hoja de firmas"
signable_id: "ID Propuesta ciudadana/Propuesta inversión"
document_numbers: "Números de documentos"
errors: errors:
models: models:
user: user:
@@ -90,4 +94,9 @@ es:
proposal_notification: proposal_notification:
attributes: attributes:
minimum_interval: minimum_interval:
invalid: "Debes esperar un mínimo de %{interval} días entre notificaciones" invalid: "Debes esperar un mínimo de %{interval} días entre notificaciones"
signature:
attributes:
document_number:
not_in_census: 'No verificado por Padrón'
already_voted: 'Ya ha votado esta propuesta'

View File

@@ -111,6 +111,7 @@ en:
settings: Configuration settings settings: Configuration settings
spending_proposals: Spending proposals spending_proposals: Spending proposals
stats: Statistics stats: Statistics
signature_sheets: Signature Sheets
moderators: moderators:
index: index:
title: Moderators title: Moderators
@@ -295,6 +296,31 @@ en:
delete: delete:
success: Geozone successfully deleted success: Geozone successfully deleted
error: This geozone can't be deleted since there are elements attached to it error: This geozone can't be deleted since there are elements attached to it
signature_sheets:
author: Author
created_at: Creation date
name: Name
no_signature_sheets: "There are not signature_sheets"
index:
title: Signature sheets
new: New signature sheets
new:
title: New signature sheets
document_numbers_note: "Write the numbers separated by commas (,)"
submit: Create signature sheet
show:
created_at: Created
author: Author
documents: Documents
document_count: "Number of documents:"
verified:
one: "There is %{count} valid signature"
other: "There are %{count} valid signatures"
unverified:
one: "There is %{count} invalid signature"
other: "There are %{count} invalid signatures"
unverified_error: (Not verified by Census)
loading: "There are still signatures that are being verified by the Census, please refresh the page in a few moments"
stats: stats:
show: show:
stats_title: Stats stats_title: Stats

View File

@@ -109,6 +109,7 @@ es:
settings: Configuración global settings: Configuración global
spending_proposals: Propuestas de inversión spending_proposals: Propuestas de inversión
stats: Estadísticas stats: Estadísticas
signature_sheets: Hojas de firmas
moderators: moderators:
index: index:
title: Moderadores title: Moderadores
@@ -293,6 +294,31 @@ es:
delete: delete:
success: Distrito borrado correctamente success: Distrito borrado correctamente
error: No se puede borrar el distrito porque ya tiene elementos asociados error: No se puede borrar el distrito porque ya tiene elementos asociados
signature_sheets:
author: Autor
created_at: Fecha de creación
name: Nombre
no_signature_sheets: "No existen hojas de firmas"
index:
title: Hojas de firmas
new: Nueva hoja de firmas
new:
title: Nueva hoja de firmas
document_numbers_note: "Introduce los números separados por comas (,)"
submit: Crear hoja de firmas
show:
created_at: Creado
author: Autor
documents: Documentos
document_count: "Número de documentos:"
verified:
one: "Hay %{count} firma válida"
other: "Hay %{count} firmas válidas"
unverified:
one: "Hay %{count} firma inválida"
other: "Hay %{count} firmas inválidas"
unverified_error: (No verificadas por el Padrón)
loading: "Aún hay firmas que se están verificando por el Padrón, por favor refresca la página en unos instantes"
stats: stats:
show: show:
stats_title: Estadísticas stats_title: Estadísticas

View File

@@ -154,6 +154,7 @@ en:
spending_proposal: Spending proposal spending_proposal: Spending proposal
user: Account user: Account
verification/sms: phone verification/sms: phone
signature_sheet: Signature sheet
geozones: geozones:
none: All city none: All city
all: All scopes all: All scopes

View File

@@ -154,6 +154,7 @@ es:
spending_proposal: la propuesta de gasto spending_proposal: la propuesta de gasto
user: la cuenta user: la cuenta
verification/sms: el teléfono verification/sms: el teléfono
signature_sheet: la hoja de firmas
geozones: geozones:
none: Toda la ciudad none: Toda la ciudad
all: Todos los ámbitos all: Todos los ámbitos

View File

@@ -1497,7 +1497,6 @@ fr:
create: create:
flash: flash:
offices: Bureaux de soutien aux citoyens offices: Bureaux de soutien aux citoyens
offices_url: http://offices.consul
success_html: Merci d'avoir demandé votre <b>code de sécurité maximum (uniquement success_html: Merci d'avoir demandé votre <b>code de sécurité maximum (uniquement
requis pour le vote final)</b>. Dans quelques jours, nous vous enverrons requis pour le vote final)</b>. Dans quelques jours, nous vous enverrons
un courrier vers votre adresse postale. Vous pouvez le réclamer dans n'importe un courrier vers votre adresse postale. Vous pouvez le réclamer dans n'importe
@@ -1512,7 +1511,6 @@ fr:
go_to_index: Voir les propositions go_to_index: Voir les propositions
office: Vérifier dans n'importe quel %{office} office: Vérifier dans n'importe quel %{office}
offices: Bureaux de soutien aux citoyens offices: Bureaux de soutien aux citoyens
offices_url: http://offices.consul
send_letter: Envoyer une lettre avec le code send_letter: Envoyer une lettre avec le code
title: Félicitations title: Félicitations
user_permission_info: Avec votre compte, vous pouvez user_permission_info: Avec votre compte, vous pouvez

View File

@@ -1518,7 +1518,6 @@ pt-BR:
create: create:
flash: flash:
offices: Escritórios de Apoio ao Cidadão offices: Escritórios de Apoio ao Cidadão
offices_url: http://offices.consul
success_html: Obrigado por solicitar seu <b>código de segurança máxima (somente success_html: Obrigado por solicitar seu <b>código de segurança máxima (somente
solicitado em votações finais)</b>. Em poucos dias nós lhe enviaremos solicitado em votações finais)</b>. Em poucos dias nós lhe enviaremos
para seu endereço relacionado nos dados que temos em arquivo. Por favor para seu endereço relacionado nos dados que temos em arquivo. Por favor
@@ -1534,7 +1533,6 @@ pt-BR:
go_to_index: Ver propostas go_to_index: Ver propostas
office: Verificar em qualquer %{office} office: Verificar em qualquer %{office}
offices: Escritórios de Apoio ao Cidadão offices: Escritórios de Apoio ao Cidadão
offices_url: http://offices.consul
send_letter: Envie-me uma carta com o código send_letter: Envie-me uma carta com o código
title: Parabéns! title: Parabéns!
user_permission_info: Com a sua conta você pode... user_permission_info: Com a sua conta você pode...

View File

@@ -9,7 +9,7 @@ en:
proposal: "Proposal created successfully." proposal: "Proposal created successfully."
proposal_notification: "Your message has been sent correctly." proposal_notification: "Your message has been sent correctly."
spending_proposal: "Spending proposal created successfully. You can access it from %{activity}" spending_proposal: "Spending proposal created successfully. You can access it from %{activity}"
signature_sheet: "Signature sheet created successfully"
save_changes: save_changes:
notice: Changes saved notice: Changes saved
update: update:

View File

@@ -9,6 +9,7 @@ es:
proposal: "Propuesta creada correctamente." proposal: "Propuesta creada correctamente."
proposal_notification: "Tu message ha sido enviado correctamente." proposal_notification: "Tu message ha sido enviado correctamente."
spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}" spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}"
signature_sheet: "Hoja de firmas creada correctamente"
save_changes: save_changes:
notice: Cambios guardados notice: Cambios guardados
update: update:

View File

@@ -27,8 +27,12 @@ en:
facebook_login: Facebook login facebook_login: Facebook login
google_login: Google login google_login: Google login
debates: Debates debates: Debates
signature_sheets: Signature sheets
spending_proposals: Investment projects spending_proposals: Investment projects
spending_proposal_features: spending_proposal_features:
voting_allowed: Voting on investment projects voting_allowed: Voting on investment projects
mailer_from_name: Origin email name mailer_from_name: Origin email name
mailer_from_address: Origin email address mailer_from_address: Origin email address
meta_description: "Site description (SEO)"
meta_keywords: "Keywords (SEO)"
verification_offices_url: Verification offices URL

View File

@@ -27,8 +27,12 @@ es:
facebook_login: Registro con Facebook facebook_login: Registro con Facebook
google_login: Registro con Google google_login: Registro con Google
debates: Debates debates: Debates
signature_sheets: Hojas de firmas
spending_proposals: Propuestas de inversión spending_proposals: Propuestas de inversión
spending_proposal_features: spending_proposal_features:
voting_allowed: Votaciones sobre propuestas de inversión. voting_allowed: Votaciones sobre propuestas de inversión.
mailer_from_name: Nombre email remitente mailer_from_name: Nombre email remitente
mailer_from_address: Dirección email remitente mailer_from_address: Dirección email remitente
meta_description: "Descripción del sitio (SEO)"
meta_keywords: "Palabras clave (SEO)"
verification_offices_url: URL oficinas verificación

View File

@@ -21,7 +21,6 @@ en:
create: create:
flash: flash:
offices: Citizen Support Offices offices: Citizen Support Offices
offices_url: http://offices.consul
success_html: Thank you for requesting your <b>maximum security code (only required for the final votes)</b>. In a few days we will send it to the address featuring in the data we have on file. Please remember that, if you prefer, you can collect your code from any of the %{offices}. success_html: Thank you for requesting your <b>maximum security code (only required for the final votes)</b>. In a few days we will send it to the address featuring in the data we have on file. Please remember that, if you prefer, you can collect your code from any of the %{offices}.
edit: edit:
see_all: See proposals see_all: See proposals
@@ -33,7 +32,6 @@ en:
go_to_index: See proposals go_to_index: See proposals
office: Verify in any %{office} office: Verify in any %{office}
offices: Citizen Support Offices offices: Citizen Support Offices
offices_url: http://offices.consul
send_letter: Send me a letter with the code send_letter: Send me a letter with the code
title: Congratulations! title: Congratulations!
user_permission_info: With your account you can... user_permission_info: With your account you can...
@@ -65,7 +63,6 @@ en:
error_not_allowed_postal_code: In order to be verified, you must be registered. error_not_allowed_postal_code: In order to be verified, you must be registered.
error_verifying_census: The Census was unable to verify your information. Please confirm that your census details are correct by calling to City Council or visit one %{offices}. error_verifying_census: The Census was unable to verify your information. Please confirm that your census details are correct by calling to City Council or visit one %{offices}.
error_verifying_census_offices: Citizen Support Office error_verifying_census_offices: Citizen Support Office
error_verifying_census_offices_url: http://offices.consul
form_errors: prevented the verification of your residence form_errors: prevented the verification of your residence
postal_code: Postcode postal_code: Postcode
postal_code_note: To verify your account you must be registered postal_code_note: To verify your account you must be registered

View File

@@ -21,7 +21,6 @@ es:
create: create:
flash: flash:
offices: Oficinas de Atención al Ciudadano offices: Oficinas de Atención al Ciudadano
offices_url: http://offices.consul
success_html: Antes de las votaciones recibirás una carta con las instrucciones para verificar tu cuenta.<br> Recuerda que puedes ahorrar el envío verificándote presencialmente en cualquiera de las %{offices}. success_html: Antes de las votaciones recibirás una carta con las instrucciones para verificar tu cuenta.<br> Recuerda que puedes ahorrar el envío verificándote presencialmente en cualquiera de las %{offices}.
edit: edit:
see_all: Ver propuestas see_all: Ver propuestas
@@ -33,7 +32,6 @@ es:
go_to_index: Ver propuestas go_to_index: Ver propuestas
office: Verificarte presencialmente en cualquier %{office} office: Verificarte presencialmente en cualquier %{office}
offices: Oficina de Atención al Ciudadano offices: Oficina de Atención al Ciudadano
offices_url: http://offices.consul
send_letter: Solicitar una carta por correo postal send_letter: Solicitar una carta por correo postal
title: "¡Felicidades!" title: "¡Felicidades!"
user_permission_info: Con tu cuenta ya puedes... user_permission_info: Con tu cuenta ya puedes...
@@ -65,7 +63,6 @@ es:
error_not_allowed_postal_code: Para verificarte debes estar empadronado. error_not_allowed_postal_code: Para verificarte debes estar empadronado.
error_verifying_census: El Padrón no pudo verificar tu información. Por favor, confirma que tus datos de empadronamiento sean correctos llamando al Ayuntamiento o visitando una %{offices}. error_verifying_census: El Padrón no pudo verificar tu información. Por favor, confirma que tus datos de empadronamiento sean correctos llamando al Ayuntamiento o visitando una %{offices}.
error_verifying_census_offices: oficina de Atención al ciudadano error_verifying_census_offices: oficina de Atención al ciudadano
error_verifying_census_offices_url: http://offices.consul
form_errors: evitaron verificar tu residencia form_errors: evitaron verificar tu residencia
postal_code: Código postal postal_code: Código postal
postal_code_note: Para verificar tus datos debes estar empadronado postal_code_note: Para verificar tus datos debes estar empadronado

View File

@@ -149,6 +149,8 @@ Rails.application.routes.draw do
get :summary, on: :collection get :summary, on: :collection
end end
resources :signature_sheets, only: [:index, :new, :create, :show]
resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do
collection { get :search} collection { get :search}
end end

View File

@@ -30,10 +30,14 @@ Setting.create(key: 'feature.spending_proposal_features.voting_allowed', value:
Setting.create(key: 'feature.twitter_login', value: "true") Setting.create(key: 'feature.twitter_login', value: "true")
Setting.create(key: 'feature.facebook_login', value: "true") Setting.create(key: 'feature.facebook_login', value: "true")
Setting.create(key: 'feature.google_login', value: "true") Setting.create(key: 'feature.google_login', value: "true")
Setting.create(key: 'feature.signature_sheets', value: "true")
Setting.create(key: 'per_page_code', value: "") Setting.create(key: 'per_page_code', value: "")
Setting.create(key: 'comments_body_max_length', value: '1000') Setting.create(key: 'comments_body_max_length', value: '1000')
Setting.create(key: 'mailer_from_name', value: 'Consul') Setting.create(key: 'mailer_from_name', value: 'Consul')
Setting.create(key: 'mailer_from_address', value: 'noreply@consul.dev') Setting.create(key: 'mailer_from_address', value: 'noreply@consul.dev')
Setting.create(key: 'meta_description', value: 'Citizen Participation and Open Government Application')
Setting.create(key: 'meta_keywords', value: 'citizen participation, open government')
Setting.create(key: 'verification_offices_url', value: 'http://oficinas-atencion-ciudadano.url/')
puts "Creating Geozones" puts "Creating Geozones"
('A'..'Z').each { |i| Geozone.create(name: "District #{i}", external_code: i.ord, census_code: i.ord) } ('A'..'Z').each { |i| Geozone.create(name: "District #{i}", external_code: i.ord, census_code: i.ord) }

View File

@@ -0,0 +1,11 @@
class CreateSignatureSheets < ActiveRecord::Migration
def change
create_table :signature_sheets do |t|
t.references :signable, polymorphic: true
t.text :document_numbers
t.boolean :processed, default: false
t.references :author
t.timestamps
end
end
end

View File

@@ -0,0 +1,11 @@
class CreateSignatures < ActiveRecord::Migration
def change
create_table :signatures do |t|
t.references :signature_sheet
t.references :user
t.string :document_number
t.boolean :verified, default: false
t.timestamps
end
end
end

View File

@@ -0,0 +1,5 @@
class AddSigntureIdToVotes < ActiveRecord::Migration
def change
add_reference :votes, :signature, index: true
end
end

View File

@@ -0,0 +1,5 @@
class AddCreatedFromSignatureToUsers < ActiveRecord::Migration
def change
add_column :users, :created_from_signature, :boolean, default: false
end
end

View File

@@ -0,0 +1,5 @@
class RemovePhysicalVotesFromProposals < ActiveRecord::Migration
def change
remove_column :proposals, :physical_votes
end
end

View File

@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161102133838) do ActiveRecord::Schema.define(version: 20161229110336) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -123,10 +123,10 @@ ActiveRecord::Schema.define(version: 20161102133838) do
t.string "visit_id" t.string "visit_id"
t.datetime "hidden_at" t.datetime "hidden_at"
t.integer "flags_count", default: 0 t.integer "flags_count", default: 0
t.datetime "ignored_flag_at"
t.integer "cached_votes_total", default: 0 t.integer "cached_votes_total", default: 0
t.integer "cached_votes_up", default: 0 t.integer "cached_votes_up", default: 0
t.integer "cached_votes_down", default: 0 t.integer "cached_votes_down", default: 0
t.datetime "ignored_flag_at"
t.integer "comments_count", default: 0 t.integer "comments_count", default: 0
t.datetime "confirmed_hide_at" t.datetime "confirmed_hide_at"
t.integer "cached_anonymous_votes_total", default: 0 t.integer "cached_anonymous_votes_total", default: 0
@@ -145,7 +145,6 @@ ActiveRecord::Schema.define(version: 20161102133838) do
add_index "debates", ["cached_votes_total"], name: "index_debates_on_cached_votes_total", using: :btree add_index "debates", ["cached_votes_total"], name: "index_debates_on_cached_votes_total", using: :btree
add_index "debates", ["cached_votes_up"], name: "index_debates_on_cached_votes_up", using: :btree add_index "debates", ["cached_votes_up"], name: "index_debates_on_cached_votes_up", using: :btree
add_index "debates", ["confidence_score"], name: "index_debates_on_confidence_score", using: :btree add_index "debates", ["confidence_score"], name: "index_debates_on_confidence_score", using: :btree
add_index "debates", ["description"], name: "index_debates_on_description", using: :btree
add_index "debates", ["geozone_id"], name: "index_debates_on_geozone_id", using: :btree add_index "debates", ["geozone_id"], name: "index_debates_on_geozone_id", using: :btree
add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree
add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree
@@ -298,7 +297,6 @@ ActiveRecord::Schema.define(version: 20161102133838) do
t.string "responsible_name", limit: 60 t.string "responsible_name", limit: 60
t.text "summary" t.text "summary"
t.string "video_url" t.string "video_url"
t.integer "physical_votes", default: 0
t.tsvector "tsv" t.tsvector "tsv"
t.integer "geozone_id" t.integer "geozone_id"
t.datetime "retired_at" t.datetime "retired_at"
@@ -310,7 +308,6 @@ ActiveRecord::Schema.define(version: 20161102133838) do
add_index "proposals", ["author_id"], name: "index_proposals_on_author_id", using: :btree add_index "proposals", ["author_id"], name: "index_proposals_on_author_id", using: :btree
add_index "proposals", ["cached_votes_up"], name: "index_proposals_on_cached_votes_up", using: :btree add_index "proposals", ["cached_votes_up"], name: "index_proposals_on_cached_votes_up", using: :btree
add_index "proposals", ["confidence_score"], name: "index_proposals_on_confidence_score", using: :btree add_index "proposals", ["confidence_score"], name: "index_proposals_on_confidence_score", using: :btree
add_index "proposals", ["description"], name: "index_proposals_on_description", using: :btree
add_index "proposals", ["geozone_id"], name: "index_proposals_on_geozone_id", using: :btree add_index "proposals", ["geozone_id"], name: "index_proposals_on_geozone_id", using: :btree
add_index "proposals", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree add_index "proposals", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree
add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree
@@ -326,6 +323,25 @@ ActiveRecord::Schema.define(version: 20161102133838) do
add_index "settings", ["key"], name: "index_settings_on_key", using: :btree add_index "settings", ["key"], name: "index_settings_on_key", using: :btree
create_table "signature_sheets", force: :cascade do |t|
t.integer "signable_id"
t.string "signable_type"
t.text "document_numbers"
t.boolean "processed", default: false
t.integer "author_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "signatures", force: :cascade do |t|
t.integer "signature_sheet_id"
t.integer "user_id"
t.string "document_number"
t.boolean "verified", default: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "spending_proposals", force: :cascade do |t| create_table "spending_proposals", force: :cascade do |t|
t.string "title" t.string "title"
t.text "description" t.text "description"
@@ -464,7 +480,8 @@ ActiveRecord::Schema.define(version: 20161102133838) do
t.boolean "email_digest", default: true t.boolean "email_digest", default: true
t.boolean "email_on_direct_message", default: true t.boolean "email_on_direct_message", default: true
t.boolean "official_position_badge", default: false t.boolean "official_position_badge", default: false
t.datetime "password_changed_at", default: '2016-11-02 13:51:14', null: false t.datetime "password_changed_at", default: '2016-12-21 17:55:08', null: false
t.boolean "created_from_signature", default: false
end end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
@@ -544,8 +561,10 @@ ActiveRecord::Schema.define(version: 20161102133838) do
t.integer "vote_weight" t.integer "vote_weight"
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "signature_id"
end end
add_index "votes", ["signature_id"], name: "index_votes_on_signature_id", using: :btree
add_index "votes", ["votable_id", "votable_type", "vote_scope"], name: "index_votes_on_votable_id_and_votable_type_and_vote_scope", using: :btree add_index "votes", ["votable_id", "votable_type", "vote_scope"], name: "index_votes_on_votable_id_and_votable_type_and_vote_scope", using: :btree
add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree

View File

@@ -57,6 +57,10 @@ Setting["org_name"] = "Consul"
# Consul installation place name (City, Country...) # Consul installation place name (City, Country...)
Setting["place_name"] = "Consul-land" Setting["place_name"] = "Consul-land"
# Meta tags for SEO
Setting["meta_description"] = nil
Setting["meta_keywords"] = nil
# Feature flags # Feature flags
Setting['feature.debates'] = true Setting['feature.debates'] = true
Setting['feature.spending_proposals'] = true Setting['feature.spending_proposals'] = true
@@ -64,6 +68,7 @@ Setting['feature.twitter_login'] = true
Setting['feature.facebook_login'] = true Setting['feature.facebook_login'] = true
Setting['feature.google_login'] = true Setting['feature.google_login'] = true
Setting['feature.public_stats'] = true Setting['feature.public_stats'] = true
Setting['feature.signature_sheets'] = true
# Spending proposals feature flags # Spending proposals feature flags
Setting['feature.spending_proposal_features.voting_allowed'] = true Setting['feature.spending_proposal_features.voting_allowed'] = true
@@ -85,3 +90,6 @@ Setting['direct_message_max_per_day'] = 3
# Email settings # Email settings
Setting['mailer_from_name'] = 'Consul' Setting['mailer_from_name'] = 'Consul'
Setting['mailer_from_address'] = 'noreply@consul.dev' Setting['mailer_from_address'] = 'noreply@consul.dev'
# Verification settings
Setting['verification_offices_url'] = 'http://oficinas-atencion-ciudadano.url/'

View File

@@ -74,7 +74,7 @@ class CensusApi
if end_point_available? if end_point_available?
client.call(:get_habita_datos, message: request(document_type, document_number)).body client.call(:get_habita_datos, message: request(document_type, document_number)).body
else else
stubbed_response_body stubbed_response(document_type, document_number)
end end
end end
@@ -97,8 +97,20 @@ class CensusApi
Rails.env.staging? || Rails.env.preproduction? || Rails.env.production? Rails.env.staging? || Rails.env.preproduction? || Rails.env.production?
end end
def stubbed_response_body def stubbed_response(document_type, document_number)
{get_habita_datos_response: {get_habita_datos_return: {hay_errores: false, datos_habitante: { item: {fecha_nacimiento_string: "31-12-1980", identificador_documento: "12345678Z", descripcion_sexo: "Varón" }}, datos_vivienda: {item: {codigo_postal: "28013", codigo_distrito: "01"}}}}} if document_number == "12345678Z" && document_type == "1"
stubbed_valid_response
else
stubbed_invalid_response
end
end
def stubbed_valid_response
{get_habita_datos_response: {get_habita_datos_return: {datos_habitante: { item: {fecha_nacimiento_string: "31-12-1980", identificador_documento: "12345678Z", descripcion_sexo: "Varón", nombre: "José", apellido1: "García" }}, datos_vivienda: {item: {codigo_postal: "28013", codigo_distrito: "01"}}}}}
end
def stubbed_invalid_response
{get_habita_datos_response: {get_habita_datos_return: {datos_habitante: {}, datos_vivienda: {}}}}
end end
def is_dni?(document_type) def is_dni?(document_type)

View File

@@ -344,4 +344,15 @@ FactoryGirl.define do
association :sender, factory: :user association :sender, factory: :user
association :receiver, factory: :user association :receiver, factory: :user
end end
factory :signature_sheet do
association :signable, factory: :proposal
association :author, factory: :user
document_numbers "123A, 456B, 789C"
end
factory :signature do
signature_sheet
sequence(:document_number) { |n| "#{n}A" }
end
end end

View File

@@ -0,0 +1,75 @@
require 'rails_helper'
feature 'Signature sheets' do
background do
admin = create(:administrator)
login_as(admin.user)
end
scenario "Index" do
3.times { create(:signature_sheet) }
visit admin_signature_sheets_path
expect(page).to have_css(".signature_sheet", count: 3)
SignatureSheet.all.each do |signature_sheet|
expect(page).to have_content signature_sheet.name
end
end
scenario 'Create' do
proposal = create(:proposal)
visit new_admin_signature_sheet_path
select "Citizen proposal", from: "signature_sheet_signable_type"
fill_in "signature_sheet_signable_id", with: proposal.id
fill_in "signature_sheet_document_numbers", with: "12345678Z, 99999999Z"
click_button "Create signature sheet"
expect(page).to have_content "Signature sheet created successfully"
visit proposal_path(proposal)
expect(page).to have_content "1 support"
end
scenario 'Errors on create' do
visit new_admin_signature_sheet_path
click_button "Create signature sheet"
expect(page).to have_content error_message
end
scenario 'Show' do
proposal = create(:proposal)
user = Administrator.first.user
signature_sheet = create(:signature_sheet,
signable: proposal,
document_numbers: "12345678Z, 123A, 123B",
author: user)
signature_sheet.verify_signatures
visit admin_signature_sheet_path(signature_sheet)
expect(page).to have_content "Citizen proposal #{proposal.id}"
expect(page).to have_content "12345678Z, 123A, 123B"
expect(page).to have_content signature_sheet.created_at.strftime("%d %b %H:%M")
expect(page).to have_content user.name
within("#document_count") do
expect(page).to have_content 3
end
within("#verified_signatures") do
expect(page).to have_content 1
end
within("#unverified_signatures") do
expect(page).to have_content 2
end
end
end

View File

@@ -47,7 +47,7 @@ feature 'DocumentVerifications' do
scenario 'Verifying a user which does exists in the census but not in the db redirects allows sending an email' do scenario 'Verifying a user which does exists in the census but not in the db redirects allows sending an email' do
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '1234' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to have_content "Please introduce the email used on the account" expect(page).to have_content "Please introduce the email used on the account"
@@ -66,7 +66,7 @@ feature 'DocumentVerifications' do
expect_any_instance_of(Verification::Management::Document).to receive(:under_sixteen?).and_return(true) expect_any_instance_of(Verification::Management::Document).to receive(:under_sixteen?).and_return(true)
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '1234' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to have_content "You must be over 16 to verify your account." expect(page).to have_content "You must be over 16 to verify your account."

View File

@@ -8,7 +8,7 @@ feature 'EmailVerifications' do
user = create(:user) user = create(:user)
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '1234' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to have_content "Please introduce the email used on the account" expect(page).to have_content "Please introduce the email used on the account"
@@ -30,7 +30,7 @@ feature 'EmailVerifications' do
expect(page).to_not have_link "Verify my account" expect(page).to_not have_link "Verify my account"
expect(page).to have_content "Account verified" expect(page).to have_content "Account verified"
expect(user.reload.document_number).to eq('1234') expect(user.reload.document_number).to eq('12345678Z')
expect(user).to be_level_three_verified expect(user).to be_level_three_verified
end end

View File

@@ -57,7 +57,7 @@ feature 'Managed User' do
user = create(:user) user = create(:user)
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '1234' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
within(".account-info") do within(".account-info") do
@@ -66,7 +66,7 @@ feature 'Managed User' do
expect(page).not_to have_content "Email" expect(page).not_to have_content "Email"
expect(page).to have_content "Document type" expect(page).to have_content "Document type"
expect(page).to have_content "Document number" expect(page).to have_content "Document number"
expect(page).to have_content "1234" expect(page).to have_content "12345678Z"
end end
expect(page).to have_content "Please introduce the email used on the account" expect(page).to have_content "Please introduce the email used on the account"
@@ -88,7 +88,7 @@ feature 'Managed User' do
login_as_manager login_as_manager
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '1234' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to have_content "Please introduce the email used on the account" expect(page).to have_content "Please introduce the email used on the account"

View File

@@ -9,7 +9,7 @@ feature 'Users' do
scenario 'Create a level 3 user from scratch' do scenario 'Create a level 3 user from scratch' do
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '1234' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to have_content "Please introduce the email used on the account" expect(page).to have_content "Please introduce the email used on the account"
@@ -45,10 +45,10 @@ feature 'Users' do
end end
scenario 'Delete a level 2 user account from document verification page', :js do scenario 'Delete a level 2 user account from document verification page', :js do
level_2_user = create(:user, :level_two, document_number: 13579) level_2_user = create(:user, :level_two, document_number: "12345678Z")
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '13579' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to_not have_content "This user account is already verified." expect(page).to_not have_content "This user account is already verified."
@@ -62,7 +62,7 @@ feature 'Users' do
expect(level_2_user.reload.erase_reason).to eq "Deleted by manager: manager_user_#{Manager.last.user_id}" expect(level_2_user.reload.erase_reason).to eq "Deleted by manager: manager_user_#{Manager.last.user_id}"
visit management_document_verifications_path visit management_document_verifications_path
fill_in 'document_verification_document_number', with: '13579' fill_in 'document_verification_document_number', with: '12345678Z'
click_button 'Check' click_button 'Check'
expect(page).to have_content "no user account associated to it" expect(page).to have_content "no user account associated to it"

View File

@@ -20,6 +20,7 @@ feature 'Verify Letter' do
end end
scenario 'Go to office instead of send letter' do scenario 'Go to office instead of send letter' do
Setting["verification_offices_url"] = "http://offices.consul"
user = create(:user, residence_verified_at: Time.current, user = create(:user, residence_verified_at: Time.current,
confirmed_phone: "611111111") confirmed_phone: "611111111")

View File

@@ -13,11 +13,6 @@ describe ProposalsHelper do
expect(progress_bar_percentage(proposal)).to eq 50 expect(progress_bar_percentage(proposal)).to eq 50
end end
it "should take into account the physical votes" do
proposal = create(:proposal, cached_votes_up: ((Proposal.votes_needed_for_success/2)-100), physical_votes: 100)
expect(progress_bar_percentage(proposal)).to eq 50
end
it "should be 100 if there are more votes than needed" do it "should be 100 if there are more votes than needed" do
proposal = create(:proposal, cached_votes_up: Proposal.votes_needed_for_success*2) proposal = create(:proposal, cached_votes_up: Proposal.votes_needed_for_success*2)
expect(progress_bar_percentage(proposal)).to eq 100 expect(progress_bar_percentage(proposal)).to eq 100
@@ -45,10 +40,6 @@ describe ProposalsHelper do
expect(supports_percentage(proposal)).to eq "100%" expect(supports_percentage(proposal)).to eq "100%"
end end
it "should take into account the physical votes" do
proposal = create(:proposal, physical_votes: Proposal.votes_needed_for_success/2)
expect(supports_percentage(proposal)).to eq "50%"
end
end end
end end

View File

@@ -0,0 +1,87 @@
require 'rails_helper'
describe SignatureSheet do
let(:signature_sheet) { build(:signature_sheet) }
describe "validations" do
it "should be valid" do
expect(signature_sheet).to be_valid
end
it "should be valid with a valid signable" do
signature_sheet.signable = create(:proposal)
expect(signature_sheet).to be_valid
signature_sheet.signable = create(:spending_proposal)
expect(signature_sheet).to be_valid
end
it "should not be valid without signable" do
signature_sheet.signable = nil
expect(signature_sheet).to_not be_valid
end
it "should not be valid without a valid signable" do
signature_sheet.signable = create(:comment)
expect(signature_sheet).to_not be_valid
end
it "should not be valid without document numbers" do
signature_sheet.document_numbers = nil
expect(signature_sheet).to_not be_valid
end
it "should not be valid without an author" do
signature_sheet.author = nil
expect(signature_sheet).to_not be_valid
end
end
describe "#name" do
it "returns name for proposal signature sheets" do
proposal = create(:proposal)
signature_sheet.signable = proposal
expect(signature_sheet.name).to eq("Citizen proposal #{proposal.id}")
end
it "returns name for spending proposal signature sheets" do
spending_proposal = create(:spending_proposal)
signature_sheet.signable = spending_proposal
expect(signature_sheet.name).to eq("Spending proposal #{spending_proposal.id}")
end
end
describe "#verify_signatures" do
it "creates signatures for each document number" do
signature_sheet = create(:signature_sheet, document_numbers: "123A, 456B")
signature_sheet.verify_signatures
expect(Signature.count).to eq(2)
end
it "marks signature sheet as processed" do
signature_sheet = create(:signature_sheet)
signature_sheet.verify_signatures
expect(signature_sheet.processed).to eq(true)
end
end
describe "#parsed_document_numbers" do
it "returns an array after spliting document numbers by newlines or commas" do
signature_sheet.document_numbers = "123A\r\n456B\n789C,123B"
expect(signature_sheet.parsed_document_numbers).to eq(['123A', '456B', '789C', '123B'])
end
it "strips spaces between number and letter" do
signature_sheet.document_numbers = "123 A\n456 B \n 789C"
expect(signature_sheet.parsed_document_numbers).to eq(['123A', '456B', '789C'])
end
end
end

View File

@@ -0,0 +1,195 @@
require 'rails_helper'
describe Signature do
let(:signature) { build(:signature) }
describe "validations" do
it "should be valid" do
expect(signature).to be_valid
end
it "should not be valid without a document number" do
signature.document_number = nil
expect(signature).to_not be_valid
signature.document_number = ""
expect(signature).to_not be_valid
signature.document_number = " "
expect(signature).to_not be_valid
end
it "should not be valid without an associated signature sheet" do
signature.signature_sheet = nil
expect(signature).to_not be_valid
end
end
describe "#clean_document_number" do
it "removes non alphanumeric characters" do
signature = create(:signature, document_number: "123-[;,9]")
expect(signature.document_number).to eq("1239")
end
it "upcases letter in document number" do
signature = create(:signature, document_number: "123a")
expect(signature.document_number).to eq("123A")
end
it "deals gracefully with empty document numbers" do
signature = build(:signature, document_number: "")
signature.clean_document_number
expect(signature.document_number).to eq("")
end
end
describe "#verified?" do
it "returns true if user exists" do
user = create(:user, :level_two, document_number: "123A")
signature = create(:signature, document_number: user.document_number)
expect(signature.verified?).to eq(true)
end
it "returns true if document number in census" do
signature = create(:signature, document_number: "12345678Z")
expect(signature.verified?).to eq(true)
end
it "returns false if user does not exist and not in census" do
signature = create(:signature, document_number: "123A")
expect(signature.verified?).to eq(false)
end
end
describe "#assign_vote" do
describe "existing user" do
it "assigns vote to user" do
user = create(:user, :level_two, document_number: "123A")
signature = create(:signature, document_number: user.document_number)
proposal = signature.signable
signature.assign_vote
expect(user.voted_for?(proposal)).to be
end
it "does not assign vote to user multiple times" do
user = create(:user, :level_two, document_number: "123A")
signature = create(:signature, document_number: user.document_number)
signature.assign_vote
signature.assign_vote
expect(Vote.count).to eq(1)
end
it "does not assign vote to user if already voted" do
proposal = create(:proposal)
user = create(:user, :level_two, document_number: "123A")
vote = create(:vote, votable: proposal, voter: user)
signature_sheet = create(:signature_sheet, signable: proposal)
signature = create(:signature, signature_sheet: signature_sheet, document_number: user.document_number)
signature.assign_vote
expect(Vote.count).to eq(1)
end
it "marks the vote as coming from a signature" do
signature = create(:signature, document_number: "12345678Z")
signature.assign_vote
expect(Vote.last.signature).to eq(signature)
end
end
describe "inexistent user" do
it "creates a user with that document number" do
signature = create(:signature, document_number: "12345678Z")
proposal = signature.signable
signature.assign_vote
user = User.last
expect(user.document_number).to eq("12345678Z")
expect(user.created_from_signature).to eq(true)
expect(user.verified_at).to be
expect(user.erased_at).to be
end
it "assign the vote to newly created user" do
signature = create(:signature, document_number: "12345678Z")
proposal = signature.signable
signature.assign_vote
user = signature.user
expect(user.voted_for?(proposal)).to be
end
it "assigns signature to vote" do
signature = create(:signature, document_number: "12345678Z")
signature.assign_vote
expect(Vote.last.signature).to eq(signature)
end
end
end
describe "#verify" do
describe "document in census" do
it "calls assign_vote" do
signature = create(:signature, document_number: "12345678Z")
expect(signature).to receive(:assign_vote)
signature.verify
end
it "sets signature as verified" do
user = create(:user, :level_two, document_number: "123A")
signature = create(:signature, document_number: user.document_number)
signature.verify
expect(signature).to be_verified
end
end
describe "document not in census" do
it "does not call assign_vote" do
signature = create(:signature, document_number: "123A")
expect(signature).to_not receive(:assign_vote)
signature.verify
end
it "maintains signature as not verified" do
signature = create(:signature, document_number: "123A")
signature.verify
expect(signature).to_not be_verified
end
end
end
end