diff --git a/app/controllers/officing/final_recounts_controller.rb b/app/controllers/officing/final_recounts_controller.rb new file mode 100644 index 000000000..56b0c600e --- /dev/null +++ b/app/controllers/officing/final_recounts_controller.rb @@ -0,0 +1,47 @@ +class Officing::FinalRecountsController < Officing::BaseController + before_action :load_poll + before_action :load_officer_assignment, only: :create + + def new + @officer_assignments = ::Poll::OfficerAssignment. + includes(:final_recounts, booth_assignment: :booth). + joins(:booth_assignment). + final. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + + @final_recounts = @officer_assignments.select {|oa| oa.final_recounts.any?}.map(&:final_recounts).flatten + end + + def create + @final_recount = ::Poll::FinalRecount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, date: final_recount_params[:date]) + @final_recount.officer_assignment_id = @officer_assignment.id + @final_recount.count = final_recount_params[:count] + + if @final_recount.save + notice = t("officing.final_recounts.flash.create") + else + notice = t("officing.final_recounts.flash.error_create") + end + redirect_to new_officing_poll_final_recount_path(@poll), notice: notice + end + + private + def load_poll + @poll = Poll.expired.find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.final.find_by(id: final_recount_params[:officer_assignment_id]) + if @officer_assignment.blank? + redirect_to new_officing_poll_final_recount_path(@poll), notice: t("officing.final_recounts.flash.error_create") + end + end + + def final_recount_params + params.permit(:officer_assignment_id, :count, :date) + end + +end \ No newline at end of file diff --git a/app/controllers/officing/polls_controller.rb b/app/controllers/officing/polls_controller.rb index 46ca9da51..e122284ec 100644 --- a/app/controllers/officing/polls_controller.rb +++ b/app/controllers/officing/polls_controller.rb @@ -5,4 +5,11 @@ class Officing::PollsController < Officing::BaseController @polls = @polls.select {|poll| poll.current?(Time.current) || poll.current?(1.day.ago)} end + def final + @polls = current_user.poll_officer? ? current_user.poll_officer.final_days_assigned_polls : [] + return unless current_user.poll_officer? + + @polls = @polls.select {|poll| poll.ends_at > 1.week.ago && poll.expired?} + end + end \ No newline at end of file diff --git a/app/controllers/officing/recounts_controller.rb b/app/controllers/officing/recounts_controller.rb index 53998e7c6..62e955d5c 100644 --- a/app/controllers/officing/recounts_controller.rb +++ b/app/controllers/officing/recounts_controller.rb @@ -28,7 +28,7 @@ class Officing::RecountsController < Officing::BaseController private def load_poll - @poll = Poll.current.find(params[:poll_id]) + @poll = Poll.find(params[:poll_id]) end def load_officer_assignment diff --git a/app/helpers/officing_helper.rb b/app/helpers/officing_helper.rb index d1436c515..5ace7ab5f 100644 --- a/app/helpers/officing_helper.rb +++ b/app/helpers/officing_helper.rb @@ -8,4 +8,12 @@ module OfficingHelper options_for_select(options) end + def booths_for_officer_select_options(officer_assignments) + options = [] + officer_assignments.each do |oa| + options << ["#{oa.booth_assignment.booth.name}", oa.id] + end + options_for_select(options) + end + end \ No newline at end of file diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb index d6295739b..214234762 100644 --- a/app/helpers/polls_helper.rb +++ b/app/helpers/polls_helper.rb @@ -23,7 +23,7 @@ module PollsHelper def poll_dates_select_options(poll) options = [] (poll.starts_at.to_date..poll.ends_at.to_date).each do |date| - options << [l(date), l(date)] + options << [l(date, format: :long), l(date)] end options_for_select(options) end diff --git a/app/models/poll/booth_assignment.rb b/app/models/poll/booth_assignment.rb index 9460533fa..057a58d27 100644 --- a/app/models/poll/booth_assignment.rb +++ b/app/models/poll/booth_assignment.rb @@ -5,6 +5,7 @@ class Poll has_many :officer_assignments, class_name: "Poll::OfficerAssignment", dependent: :destroy has_many :recounts, class_name: "Poll::Recount", dependent: :destroy + has_many :final_recounts, class_name: "Poll::FinalRecount", dependent: :destroy has_many :officers, through: :officer_assignments has_many :voters end diff --git a/app/models/poll/final_recount.rb b/app/models/poll/final_recount.rb index af2991308..6ebf5eede 100644 --- a/app/models/poll/final_recount.rb +++ b/app/models/poll/final_recount.rb @@ -4,7 +4,7 @@ class Poll belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" validates :booth_assignment_id, presence: true - validates :officer_assignment_id, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} validates :count, presence: true, numericality: {only_integer: true} before_save :update_logs diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb index 0eb68beb8..736deb54b 100644 --- a/app/models/poll/officer_assignment.rb +++ b/app/models/poll/officer_assignment.rb @@ -3,6 +3,7 @@ class Poll belongs_to :officer belongs_to :booth_assignment has_one :recount + has_many :final_recounts has_many :voters validates :officer_id, presence: true diff --git a/app/views/officing/_menu.html.erb b/app/views/officing/_menu.html.erb index 40484a90a..d6773a426 100644 --- a/app/views/officing/_menu.html.erb +++ b/app/views/officing/_menu.html.erb @@ -8,11 +8,18 @@ <% end %> -
  • > +
  • > <%= link_to officing_polls_path do %> <%= t("officing.menu.recounts") %> <% end %>
  • + +
  • > + <%= link_to final_officing_polls_path do %> + + <%= t("officing.menu.final_recounts") %> + <% end %> +
  • diff --git a/app/views/officing/final_recounts/new.html.erb b/app/views/officing/final_recounts/new.html.erb new file mode 100644 index 000000000..42518cfd9 --- /dev/null +++ b/app/views/officing/final_recounts/new.html.erb @@ -0,0 +1,72 @@ +<% if @officer_assignments.any? %> +

    <%= t("officing.final_recounts.new.title", poll: @poll.name) %>

    + + <%= form_tag(officing_poll_final_recounts_path(@poll), {id: "officer_assignment_form"}) do %> +
    +
    + + <%= select_tag :officer_assignment_id, + booths_for_officer_select_options(@officer_assignments), + { prompt: t("officing.final_recounts.new.select_booth"), + label: false } %> +
    +
    + +
    +
    + + <%= select_tag :date, + poll_dates_select_options(@poll), + { prompt: t("officing.final_recounts.new.select_date"), + label: false } %> +
    +
    + +
    +
    + + <%= text_field_tag :count, nil, placeholder: t("officing.final_recounts.new.count_placeholder") %> +
    +
    + +
    +
    + <%= submit_tag t("officing.final_recounts.new.submit"), class: "button expanded" %> +
    +
    + <% end %> +<% else %> +

    <%= @poll.name %>

    +
    + <%= t("officing.final_recounts.new.not_allowed") %> +
    +<% end %> + + +<% if @final_recounts.any? %> +
    +

    <%= t("officing.final_recounts.new.final_recount_list") %>

    + + + + + + + + + <% @final_recounts.each do |final_recount| %> + + + + + + <% end %> + +
    <%= t("officing.final_recounts.new.date") %><%= t("officing.final_recounts.new.booth") %><%= t("officing.final_recounts.new.count") %>
    + <%= l(final_recount.date.to_date, format: :long) %> + + <%= final_recount.booth_assignment.booth.name %> + + <%= final_recount.count %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/officing/polls/final.html.erb b/app/views/officing/polls/final.html.erb new file mode 100644 index 000000000..7159a3769 --- /dev/null +++ b/app/views/officing/polls/final.html.erb @@ -0,0 +1,30 @@ +

    <%= t("officing.polls.final.title") %>

    + +<% if @polls.any? %> + + + + + + + <% @polls.each do |poll| %> + + + + + <% end %> + +
    <%= t("officing.polls.final.select_poll") %> 
    + + <%= link_to poll.name, new_officing_poll_final_recount_path(poll) %> + + + <%= link_to t("officing.polls.final.add_recount"), + new_officing_poll_final_recount_path(poll), + class: "button hollow" %> +
    +<% else %> +
    + <%= t("officing.polls.final.no_polls") %> +
    +<% end %> \ No newline at end of file diff --git a/config/locales/officing.en.yml b/config/locales/officing.en.yml index 4f320a125..c82d3f740 100644 --- a/config/locales/officing.en.yml +++ b/config/locales/officing.en.yml @@ -8,12 +8,18 @@ en: menu: voters: Validate document recounts: Store recount + final_recounts: Final recounts polls: index: title: Poll list no_polls: You are not officing in any active poll select_poll: Select poll add_recount: Add recount + final: + title: Polls ready for final recounting + no_polls: You are not officing final recounts in any active poll + select_poll: Select poll + add_recount: Add final recount recounts: flash: create: "Data added" @@ -29,6 +35,21 @@ en: recount_list: "Your recounts" booth: "Booth" date: "Date" + final_recounts: + flash: + create: "Data added" + error_create: "Final counts NOT added. Error in data." + new: + title: "%{poll} - Add final recount" + not_allowed: "You are allowed to add final recounts for this poll" + booth: "Booth" + date: "Date" + select_booth: "Select booth" + select_date: "Select date" + count: "Vote count" + count_placeholder: "Vote count" + submit: Save + final_recount_list: "Your final recounts" residence: flash: create: "Document verified with Census" diff --git a/config/locales/officing.es.yml b/config/locales/officing.es.yml index 28fc1595d..df91b79ed 100644 --- a/config/locales/officing.es.yml +++ b/config/locales/officing.es.yml @@ -8,12 +8,18 @@ es: menu: voters: "Validar documento" recounts: "Guardar recuento" + final_recounts: "Recuentos finales" polls: index: title: "Listado de votaciones" no_polls: "No eres presidente de mesa en ninguna votación activa" select_poll: "Selecciona votación" add_recount: "Añadir recuento" + final: + title: "Listado de votaciones finalizadas" + no_polls: "No tienes permiso para recuento final en ninguna votación reciente" + select_poll: "Selecciona votación" + add_recount: "Añadir recuentos finales" recounts: flash: create: "Datos añadidos" @@ -29,6 +35,21 @@ es: recount_list: "Tus recuentos" booth: "Urna" date: "Fecha" + final_recounts: + flash: + create: "Datos añadidos" + error_create: "Recuento final NO añadido. Error en los datos" + new: + title: "%{poll} - Añadir recuento final" + not_allowed: "No tienes permiso para introducir recountos finales" + booth: "Urna" + date: "Día" + select_booth: "Elige urna" + select_date: "Elige día" + count: "Número de votos" + count_placeholder: "Número de votos" + submit: "Guardar" + final_recount_list: "Tus recuentos finales" residence: flash: create: "Documento verificado con el Padrón" diff --git a/config/routes.rb b/config/routes.rb index 2a0a157c2..8be55feb5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -332,7 +332,10 @@ Rails.application.routes.draw do namespace :officing do resources :polls, only: [:index] do + get :final, on: :collection + resources :recounts, only: [:new, :create] + resources :final_recounts, only: [:new, :create] end resource :residence, controller: "residence", only: [:new, :create] resources :voters, only: [:new, :create] diff --git a/db/migrate/20170130163030_change_datetimes_to_date_in_recounts_and_assignments.rb b/db/migrate/20170130163030_change_datetimes_to_date_in_recounts_and_assignments.rb new file mode 100644 index 000000000..2ef6d0d5a --- /dev/null +++ b/db/migrate/20170130163030_change_datetimes_to_date_in_recounts_and_assignments.rb @@ -0,0 +1,13 @@ +class ChangeDatetimesToDateInRecountsAndAssignments < ActiveRecord::Migration + def up + change_column :poll_recounts, :date, :date, null: false + change_column :poll_final_recounts, :date, :date, null: false + change_column :poll_officer_assignments, :date, :date, null: false + end + + def down + change_column :poll_recounts, :date, :datetime, null: false + change_column :poll_final_recounts, :date, :datetime, null: false + change_column :poll_officer_assignments, :date, :datetime, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 72ab9271d..88a43f82f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170130133736) do +ActiveRecord::Schema.define(version: 20170130163030) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -406,7 +406,7 @@ ActiveRecord::Schema.define(version: 20170130133736) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.text "officer_assignment_id_log", default: "" - t.datetime "date", null: false + t.date "date", null: false end add_index "poll_final_recounts", ["booth_assignment_id"], name: "index_poll_final_recounts_on_booth_assignment_id", using: :btree @@ -416,7 +416,7 @@ ActiveRecord::Schema.define(version: 20170130133736) do t.integer "officer_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "date" + t.date "date", null: false t.boolean "final", default: false end @@ -465,7 +465,7 @@ ActiveRecord::Schema.define(version: 20170130133736) do t.text "count_log", default: "" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "date" + t.date "date", null: false t.text "officer_assignment_id_log", default: "" end diff --git a/spec/factories.rb b/spec/factories.rb index dc3b4ff58..3ead9bf0e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -415,6 +415,10 @@ FactoryGirl.define do association :officer, factory: :poll_officer association :booth_assignment, factory: :poll_booth_assignment date Time.current.to_date + + trait :final do + final true + end end factory :poll_recount, class: 'Poll::Recount' do @@ -424,6 +428,13 @@ FactoryGirl.define do date (1.month.ago.to_datetime..1.month.from_now.to_datetime).to_a.sample end + factory :poll_final_recount, class: 'Poll::FinalRecount' do + association :officer_assignment, factory: [:poll_officer_assignment, :final] + association :booth_assignment, factory: :poll_booth_assignment + count (1..100).to_a.sample + date (1.month.ago.to_datetime..1.month.from_now.to_datetime).to_a.sample + end + factory :poll_voter, class: 'Poll::Voter' do poll association :user, :level_two diff --git a/spec/features/admin/poll/officer_assignments_spec.rb b/spec/features/admin/poll/officer_assignments_spec.rb index 3cbf9fb13..53b174b9a 100644 --- a/spec/features/admin/poll/officer_assignments_spec.rb +++ b/spec/features/admin/poll/officer_assignments_spec.rb @@ -30,7 +30,7 @@ feature 'Admin officer assignments in poll' do expect(page).to have_content booth_assignment.poll.name within('#officer_assignment_form') do - select I18n.l(booth_assignment.poll.ends_at.to_date), from: 'date' + select I18n.l(booth_assignment.poll.ends_at.to_date, format: :long), from: 'date' select "#{booth_assignment.booth.name} (#{booth_assignment.booth.location})", from: 'booth_id' click_button 'Add shift' end diff --git a/spec/features/officing/final_recount_spec.rb b/spec/features/officing/final_recount_spec.rb new file mode 100644 index 000000000..770561bb7 --- /dev/null +++ b/spec/features/officing/final_recount_spec.rb @@ -0,0 +1,100 @@ +require 'rails_helper' + +feature 'Officing Final Recount' do + + background do + @poll_officer = create(:poll_officer) + @officer_assignment = create(:poll_officer_assignment, :final, officer: @poll_officer) + @poll = @officer_assignment.booth_assignment.poll + @poll.update(ends_at: 1.day.ago) + login_as(@poll_officer.user) + end + + scenario 'Only polls where user is officer for final recounts are accessible' do + regular_officer_assignment_1 = create(:poll_officer_assignment, officer: @poll_officer) + regular_officer_assignment_2 = create(:poll_officer_assignment, officer: @poll_officer) + + not_allowed_poll_1 = create(:poll, :expired) + not_allowed_poll_2 = regular_officer_assignment_1.booth_assignment.poll + not_allowed_poll_2.update(ends_at: 1.day.ago) + not_allowed_poll_3 = regular_officer_assignment_2.booth_assignment.poll + + visit root_path + click_link 'Polling officers' + + expect(page).to have_content('Poll officing') + within('#side_menu') do + click_link 'Final recounts' + end + + expect(page).to_not have_content(not_allowed_poll_1.name) + expect(page).to_not have_content(not_allowed_poll_2.name) + expect(page).to_not have_content(not_allowed_poll_3.name) + expect(page).to have_content(@poll.name) + + visit new_officing_poll_final_recount_path(not_allowed_poll_1) + expect(page).to have_content('You are allowed to add final recounts for this poll') + end + + scenario 'Add final recount' do + visit officing_root_path + + within('#side_menu') do + click_link 'Final recounts' + end + + click_link @poll.name + + expect(page).to_not have_content('Your recounts') + + booth_name = @officer_assignment.booth_assignment.booth.name + date = I18n.l(@poll.starts_at.to_date, format: :long) + select booth_name, from: 'officer_assignment_id' + select date, from: 'date' + fill_in :count, with: '33' + click_button 'Save' + + expect(page).to have_content('Your final recounts') + + within("#poll_final_recount_#{@officer_assignment.booth_assignment.final_recounts.first.id}") do + expect(page).to have_content(date) + expect(page).to have_content(booth_name) + expect(page).to have_content('33') + end + end + + scenario 'Edit recount' do + final_recount = create(:poll_final_recount, + officer_assignment: @officer_assignment, + booth_assignment: @officer_assignment.booth_assignment, + date: @poll.starts_at, + count: 100) + + booth_name = @officer_assignment.booth_assignment.booth.name + date = I18n.l(final_recount.date.to_date, format: :long) + + visit new_officing_poll_final_recount_path(@poll) + + expect(page).to have_content('Your final recounts') + + within("#poll_final_recount_#{final_recount.id}") do + expect(page).to have_content(date) + expect(page).to have_content(booth_name) + expect(page).to have_content('100') + end + + select booth_name, from: 'officer_assignment_id' + select date, from: 'date' + fill_in :count, with: '42' + click_button 'Save' + + expect(page).to have_content "Data added" + + within("#poll_final_recount_#{final_recount.id}") do + expect(page).to have_content(date) + expect(page).to have_content(booth_name) + expect(page).to have_content('42') + end + expect(page).to_not have_content('100') + end +end \ No newline at end of file diff --git a/spec/models/poll/final_recount_spec.rb b/spec/models/poll/final_recount_spec.rb new file mode 100644 index 000000000..7239056a5 --- /dev/null +++ b/spec/models/poll/final_recount_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +describe :final_recount do + + it "should update count_log if count changes" do + final_recount = create(:poll_final_recount, count: 33) + + expect(final_recount.count_log).to eq("") + + final_recount.count = 33 + final_recount.save + final_recount.count = 32 + final_recount.save + final_recount.count = 34 + final_recount.save + + expect(final_recount.count_log).to eq(":33:32") + end + + it "should update officer_assignment_id_log if count changes" do + final_recount = create(:poll_final_recount, count: 33) + + expect(final_recount.count_log).to eq("") + + final_recount.count = 33 + final_recount.officer_assignment_id = 1 + final_recount.save + + final_recount.count = 32 + final_recount.officer_assignment_id = 2 + final_recount.save + + final_recount.count = 34 + final_recount.officer_assignment_id = 3 + final_recount.save + + expect(final_recount.officer_assignment_id_log).to eq(":1:2") + end + +end \ No newline at end of file