diff --git a/app/controllers/admin/signature_sheets_controller.rb b/app/controllers/admin/signature_sheets_controller.rb new file mode 100644 index 000000000..60299c5a6 --- /dev/null +++ b/app/controllers/admin/signature_sheets_controller.rb @@ -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 \ No newline at end of file diff --git a/app/helpers/signature_sheets_helper.rb b/app/helpers/signature_sheets_helper.rb new file mode 100644 index 000000000..acd75a5ab --- /dev/null +++ b/app/helpers/signature_sheets_helper.rb @@ -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 \ No newline at end of file diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 1dbc5baa2..ea1d4fa40 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,5 +1,5 @@ class ApplicationMailer < ActionMailer::Base helper :settings - default from: "Consul " + default from: "#{Setting['mailer_from_name']} <#{Setting['mailer_from_address']}>" layout 'mailer' end diff --git a/app/models/signature.rb b/app/models/signature.rb new file mode 100644 index 000000000..47408858a --- /dev/null +++ b/app/models/signature.rb @@ -0,0 +1,85 @@ +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 + + 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 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 \ No newline at end of file diff --git a/app/models/signature_sheet.rb b/app/models/signature_sheet.rb new file mode 100644 index 000000000..6852820bb --- /dev/null +++ b/app/models/signature_sheet.rb @@ -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 = signatures.create(document_number: document_number) + signature.verify + end + update(processed: true) + end + + def parsed_document_numbers + document_numbers.split(/\W+/) + end + + def signable_found + errors.add(:signable_id, :not_found) if errors.messages[:signable].present? + end +end \ No newline at end of file diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index 93adbc03c..6da3b23fd 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -35,6 +35,14 @@ <% end %> + <% if feature?(:signature_sheets) %> +
  • > + <%= link_to admin_signature_sheets_path do %> + <%= t("admin.menu.signature_sheets") %> + <% end %> +
  • + <% end %> + <% if feature?(:legislation) %>
  • > <%= link_to admin_legislation_processes_path do %> diff --git a/app/views/admin/signature_sheets/index.html.erb b/app/views/admin/signature_sheets/index.html.erb new file mode 100644 index 000000000..74241b5b0 --- /dev/null +++ b/app/views/admin/signature_sheets/index.html.erb @@ -0,0 +1,31 @@ +

    <%= t("admin.signature_sheets.index.title") %>

    + +<%= link_to t("admin.signature_sheets.index.new"), new_admin_signature_sheet_path, + class: "button success float-right" %> + +<% if @signature_sheets.any? %> + + + + + + + <% @signature_sheets.each do |signature_sheet| %> + + + + + + <% end %> +
    <%= t("admin.signature_sheets.name") %><%= t("admin.signature_sheets.author") %><%= t("admin.signature_sheets.created_at") %>
    + <%= link_to signature_sheet.name, [:admin, signature_sheet] %> + + <%= signature_sheet.author.name %> + + <%= l(signature_sheet.created_at, format: :short) %> +
    +<% else %> +
    + <%= t("admin.signature_sheets.no_signature_sheets") %> +
    +<% end %> diff --git a/app/views/admin/signature_sheets/new.html.erb b/app/views/admin/signature_sheets/new.html.erb new file mode 100644 index 000000000..f471e1ea5 --- /dev/null +++ b/app/views/admin/signature_sheets/new.html.erb @@ -0,0 +1,22 @@ +<%= render 'shared/back_link' %> + +

    <%= t("admin.signature_sheets.new.title") %>

    + +<%= form_for [:admin, @signature_sheet] do |f| %> + <%= render 'shared/errors', + resource: @signature_sheet %> + +
    + <%= f.select :signable_type, signable_options %> +
    + +
    + <%= f.text_field :signable_id %> +
    + + <%= f.label :document_numbers %> +

    <%= t("admin.signature_sheets.new.document_numbers_note") %>

    + <%= f.text_area :document_numbers, rows: "6", label: false %> + + <%= f.submit(class: "button", value: t("admin.signature_sheets.new.submit")) %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/signature_sheets/show.html.erb b/app/views/admin/signature_sheets/show.html.erb new file mode 100644 index 000000000..a3787b67d --- /dev/null +++ b/app/views/admin/signature_sheets/show.html.erb @@ -0,0 +1,39 @@ +

    <%= @signature_sheet.name %>

    + +
    + <%= t("admin.signature_sheets.show.created_at") %> + <%= l(@signature_sheet.created_at, format: :short) %> +  •  + <%= t("admin.signature_sheets.show.author") %> + <%= @signature_sheet.author.name %> +
    + +
    +

    <%= t("admin.signature_sheets.show.documents") %>

    + <%= simple_format @signature_sheet.document_numbers %> +
    + + +
    + + <%= t("admin.signature_sheets.show.verified", + count: @signature_sheet.signatures.verified.count ) %> + +
    + +
    +

    + + <%= t("admin.signature_sheets.show.unverified", + count: @signature_sheet.signatures.unverified.count ) %> + <%= t("admin.signature_sheets.show.unverified_error") %> + +

    + <%= @signature_sheet.signatures.unverified.map(&:document_number).join(", ") %> +
    + +<% unless @signature_sheet.processed? %> +
    + <%= t("admin.signature_sheets.show.loading") %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/debates/_form.html.erb b/app/views/debates/_form.html.erb index 30152b366..c88045f26 100644 --- a/app/views/debates/_form.html.erb +++ b/app/views/debates/_form.html.erb @@ -1,6 +1,5 @@ <%= form_for(@debate) do |f| %> - <%= render 'shared/errors', resource: @debate %>
    diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 144a2ab58..cd2f77a0b 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -95,6 +95,8 @@ search: # - '{devise,simple_form}.*' ignore_missing: - 'unauthorized.*' + - 'activerecord.models.proposal' + - 'activerecord.models.spending_proposal' - 'activerecord.errors.models.proposal_notification.*' - 'activerecord.errors.models.direct_message.*' - 'errors.messages.blank' diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index edabf24d4..32f387ba9 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -12,7 +12,11 @@ Devise.setup do |config| # 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 # with default "from" parameter. - config.mailer_sender = 'noreply@consul.es' + if Rails.env.test? + config.mailer_sender = "noreply@example.org" + else + config.mailer_sender = "#{Setting['mailer_from_name']} <#{Setting['mailer_from_address']}>" + end # Configure the class responsible to send e-mails. config.mailer = 'DeviseMailer' diff --git a/config/initializers/vote_extensions.rb b/config/initializers/vote_extensions.rb index 345cb8f01..fce7b49eb 100644 --- a/config/initializers/vote_extensions.rb +++ b/config/initializers/vote_extensions.rb @@ -1,4 +1,6 @@ ActsAsVotable::Vote.class_eval do + belongs_to :signature + def self.for_debates(debates) where(votable_type: 'Debate', votable_id: debates) end @@ -14,4 +16,5 @@ ActsAsVotable::Vote.class_eval do def value vote_flag end + end diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index ad7b20e8d..d39851fcc 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -75,6 +75,10 @@ en: external_url: "Link to additional documentation" geozone_id: "Scope of operation" title: "Title" + signature_sheet: + signable_type: "Signable type" + signable_id: "Signable ID" + document_numbers: "Documents numbers" legislation/process: title: Process Title description: Description @@ -117,3 +121,8 @@ en: attributes: minimum_interval: 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' diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index 7d6147a54..f18c339de 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -75,6 +75,10 @@ es: external_url: "Enlace a documentación adicional" geozone_id: "Ámbito de actuación" 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" legislation/process: title: Título del proceso description: En qué consiste @@ -117,3 +121,8 @@ es: attributes: minimum_interval: 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' diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 9d0e76226..58fbe7ce0 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -187,6 +187,7 @@ en: settings: Configuration settings spending_proposals: Spending proposals stats: Statistics + signature_sheets: Signature Sheets legislation: Collaborative Legislation moderators: index: @@ -372,6 +373,30 @@ en: delete: success: Geozone successfully deleted 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 + 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: show: stats_title: Stats diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 03d14c820..16dda1ec2 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -185,6 +185,7 @@ es: settings: Configuración global spending_proposals: Propuestas de inversión stats: Estadísticas + signature_sheets: Hojas de firmas legislation: Legislación colaborativa moderators: index: @@ -370,6 +371,30 @@ es: delete: success: Distrito borrado correctamente 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 + 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: show: stats_title: Estadísticas diff --git a/config/locales/en.yml b/config/locales/en.yml index cf076656f..7381cd9ad 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -154,6 +154,7 @@ en: spending_proposal: Spending proposal user: Account verification/sms: phone + signature_sheet: Signature sheet geozones: none: All city all: All scopes diff --git a/config/locales/es.yml b/config/locales/es.yml index 0238645b6..5b535ddd0 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -154,6 +154,7 @@ es: spending_proposal: la propuesta de gasto user: la cuenta verification/sms: el teléfono + signature_sheet: la hoja de firmas geozones: none: Toda la ciudad all: Todos los ámbitos diff --git a/config/locales/responders.en.yml b/config/locales/responders.en.yml index ab1641799..f69d95c5f 100755 --- a/config/locales/responders.en.yml +++ b/config/locales/responders.en.yml @@ -9,7 +9,7 @@ en: proposal: "Proposal created successfully." proposal_notification: "Your message has been sent correctly." spending_proposal: "Spending proposal created successfully. You can access it from %{activity}" - + signature_sheet: "Signature sheet created successfully" save_changes: notice: Changes saved update: diff --git a/config/locales/responders.es.yml b/config/locales/responders.es.yml index 387085d69..4aeb969d9 100644 --- a/config/locales/responders.es.yml +++ b/config/locales/responders.es.yml @@ -9,6 +9,7 @@ es: proposal: "Propuesta creada correctamente." proposal_notification: "Tu message ha sido enviado correctamente." spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}" + signature_sheet: "Hoja de firmas creada correctamente" save_changes: notice: Cambios guardados update: diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index f3cfe261f..d9cfc8b78 100755 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -27,7 +27,10 @@ en: facebook_login: Facebook login google_login: Google login debates: Debates + signature_sheets: Signature sheets spending_proposals: Investment projects spending_proposal_features: voting_allowed: Voting on investment projects legislation: Legislation + mailer_from_name: Origin email name + mailer_from_address: Origin email address diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index 78dfb8912..728eefdd3 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -27,7 +27,10 @@ es: facebook_login: Registro con Facebook google_login: Registro con Google debates: Debates + signature_sheets: Hojas de firmas spending_proposals: Propuestas de inversión spending_proposal_features: voting_allowed: Votaciones sobre propuestas de inversión. legislation: Legislación + mailer_from_name: Nombre email remitente + mailer_from_address: Dirección email remitente diff --git a/config/routes.rb b/config/routes.rb index 731d0323a..5b2fcf9d6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -169,6 +169,8 @@ Rails.application.routes.draw do get :summary, on: :collection end + resources :signature_sheets, only: [:index, :new, :create, :show] + resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do collection { get :search} end diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 08d19a688..5450ee3a8 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -30,9 +30,13 @@ Setting.create(key: 'feature.spending_proposal_features.voting_allowed', value: Setting.create(key: 'feature.twitter_login', value: "true") Setting.create(key: 'feature.facebook_login', value: "true") Setting.create(key: 'feature.google_login', value: "true") +Setting.create(key: 'feature.signature_sheets', value: "true") Setting.create(key: 'feature.legislation', value: "true") Setting.create(key: 'per_page_code', value: "") Setting.create(key: 'comments_body_max_length', value: '1000') +Setting.create(key: 'mailer_from_name', value: 'Consul') +Setting.create(key: 'mailer_from_address', value: 'noreply@consul.dev') + puts "Creating Geozones" ('A'..'Z').each { |i| Geozone.create(name: "District #{i}", external_code: i.ord, census_code: i.ord) } diff --git a/db/migrate/20161214212918_create_signature_sheets.rb b/db/migrate/20161214212918_create_signature_sheets.rb new file mode 100644 index 000000000..d34728a34 --- /dev/null +++ b/db/migrate/20161214212918_create_signature_sheets.rb @@ -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 \ No newline at end of file diff --git a/db/migrate/20161214233817_create_signatures.rb b/db/migrate/20161214233817_create_signatures.rb new file mode 100644 index 000000000..fd6b53d3a --- /dev/null +++ b/db/migrate/20161214233817_create_signatures.rb @@ -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 diff --git a/db/migrate/20161221131403_add_signture_id_to_votes.rb b/db/migrate/20161221131403_add_signture_id_to_votes.rb new file mode 100644 index 000000000..28851fdac --- /dev/null +++ b/db/migrate/20161221131403_add_signture_id_to_votes.rb @@ -0,0 +1,5 @@ +class AddSigntureIdToVotes < ActiveRecord::Migration + def change + add_reference :votes, :signature, index: true + end +end diff --git a/db/migrate/20161221151239_add_created_from_signature_to_users.rb b/db/migrate/20161221151239_add_created_from_signature_to_users.rb new file mode 100644 index 000000000..214bd43cb --- /dev/null +++ b/db/migrate/20161221151239_add_created_from_signature_to_users.rb @@ -0,0 +1,5 @@ +class AddCreatedFromSignatureToUsers < ActiveRecord::Migration + def change + add_column :users, :created_from_signature, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 96001434c..0438a3328 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -123,10 +123,10 @@ ActiveRecord::Schema.define(version: 20161222180927) do t.string "visit_id" t.datetime "hidden_at" t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" t.integer "cached_votes_total", default: 0 t.integer "cached_votes_up", default: 0 t.integer "cached_votes_down", default: 0 - t.datetime "ignored_flag_at" t.integer "comments_count", default: 0 t.datetime "confirmed_hide_at" t.integer "cached_anonymous_votes_total", default: 0 @@ -145,7 +145,6 @@ ActiveRecord::Schema.define(version: 20161222180927) do 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", ["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", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree add_index "debates", ["hot_score"], name: "index_debates_on_hot_score", using: :btree @@ -382,7 +381,6 @@ ActiveRecord::Schema.define(version: 20161222180927) do 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", ["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", ["hidden_at"], name: "index_proposals_on_hidden_at", using: :btree add_index "proposals", ["hot_score"], name: "index_proposals_on_hot_score", using: :btree @@ -398,6 +396,25 @@ ActiveRecord::Schema.define(version: 20161222180927) do 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| t.string "title" t.text "description" @@ -536,7 +553,8 @@ ActiveRecord::Schema.define(version: 20161222180927) do t.boolean "email_digest", default: true t.boolean "email_on_direct_message", default: true 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 add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree @@ -616,8 +634,10 @@ ActiveRecord::Schema.define(version: 20161222180927) do t.integer "vote_weight" t.datetime "created_at" t.datetime "updated_at" + t.integer "signature_id" 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", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree diff --git a/db/seeds.rb b/db/seeds.rb index 82d6d6cdd..14bf7cb24 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -64,6 +64,7 @@ Setting['feature.twitter_login'] = true Setting['feature.facebook_login'] = true Setting['feature.google_login'] = true Setting['feature.public_stats'] = true +Setting['feature.signature_sheets'] = true Setting['feature.legislation'] = true # Spending proposals feature flags @@ -82,3 +83,7 @@ Setting['banner-img.banner-img-three'] = "Banner image 3" # Proposal notifications Setting['proposal_notification_minimum_interval_in_days'] = 3 Setting['direct_message_max_per_day'] = 3 + +# Email settings +Setting['mailer_from_name'] = 'Consul' +Setting['mailer_from_address'] = 'noreply@consul.dev' diff --git a/lib/census_api.rb b/lib/census_api.rb index 678a748c6..7f88557f2 100644 --- a/lib/census_api.rb +++ b/lib/census_api.rb @@ -74,7 +74,7 @@ class CensusApi if end_point_available? client.call(:get_habita_datos, message: request(document_type, document_number)).body else - stubbed_response_body + stubbed_response(document_type, document_number) end end @@ -97,8 +97,20 @@ class CensusApi Rails.env.staging? || Rails.env.preproduction? || Rails.env.production? end - def stubbed_response_body - {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"}}}}} + def stubbed_response(document_type, document_number) + 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 def is_dni?(document_type) diff --git a/spec/factories.rb b/spec/factories.rb index 98730380f..f14edf6ea 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -344,6 +344,17 @@ FactoryGirl.define do association :receiver, factory: :user 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 + factory :legislation_process, class: 'Legislation::Process' do title "A collaborative legislation process" description "Description of the process" diff --git a/spec/features/admin/signature_sheets_spec.rb b/spec/features/admin/signature_sheets_spec.rb new file mode 100644 index 000000000..a2474834a --- /dev/null +++ b/spec/features/admin/signature_sheets_spec.rb @@ -0,0 +1,71 @@ +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("#verified_signatures") do + expect(page).to have_content 1 + end + + within("#unverified_signatures") do + expect(page).to have_content 2 + end + end + +end \ No newline at end of file diff --git a/spec/features/management/document_verifications_spec.rb b/spec/features/management/document_verifications_spec.rb index 674349815..130630762 100644 --- a/spec/features/management/document_verifications_spec.rb +++ b/spec/features/management/document_verifications_spec.rb @@ -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 visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '1234' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' 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) visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '1234' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' expect(page).to have_content "You must be over 16 to verify your account." diff --git a/spec/features/management/email_verifications_spec.rb b/spec/features/management/email_verifications_spec.rb index 22987f317..d68d9bd5f 100644 --- a/spec/features/management/email_verifications_spec.rb +++ b/spec/features/management/email_verifications_spec.rb @@ -8,7 +8,7 @@ feature 'EmailVerifications' do user = create(:user) visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '1234' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' 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 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 end diff --git a/spec/features/management/managed_users_spec.rb b/spec/features/management/managed_users_spec.rb index 78010bc39..311b8f5cd 100644 --- a/spec/features/management/managed_users_spec.rb +++ b/spec/features/management/managed_users_spec.rb @@ -57,7 +57,7 @@ feature 'Managed User' do user = create(:user) visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '1234' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' within(".account-info") do @@ -66,7 +66,7 @@ feature 'Managed User' do expect(page).not_to have_content "Email" expect(page).to have_content "Document type" expect(page).to have_content "Document number" - expect(page).to have_content "1234" + expect(page).to have_content "12345678Z" end expect(page).to have_content "Please introduce the email used on the account" @@ -88,7 +88,7 @@ feature 'Managed User' do login_as_manager visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '1234' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' expect(page).to have_content "Please introduce the email used on the account" diff --git a/spec/features/management/users_spec.rb b/spec/features/management/users_spec.rb index 1a0618e60..694720cc0 100644 --- a/spec/features/management/users_spec.rb +++ b/spec/features/management/users_spec.rb @@ -9,7 +9,7 @@ feature 'Users' do scenario 'Create a level 3 user from scratch' do visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '1234' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' expect(page).to have_content "Please introduce the email used on the account" @@ -45,10 +45,10 @@ feature 'Users' do end 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 - fill_in 'document_verification_document_number', with: '13579' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' 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}" visit management_document_verifications_path - fill_in 'document_verification_document_number', with: '13579' + fill_in 'document_verification_document_number', with: '12345678Z' click_button 'Check' expect(page).to have_content "no user account associated to it" diff --git a/spec/models/signature_sheet_spec.rb b/spec/models/signature_sheet_spec.rb new file mode 100644 index 000000000..d8f5c3513 --- /dev/null +++ b/spec/models/signature_sheet_spec.rb @@ -0,0 +1,81 @@ +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 + end + +end \ No newline at end of file diff --git a/spec/models/signature_spec.rb b/spec/models/signature_spec.rb new file mode 100644 index 000000000..6f7237484 --- /dev/null +++ b/spec/models/signature_spec.rb @@ -0,0 +1,171 @@ +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 + 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 "#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 \ No newline at end of file