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 %>
+
+
+ <%= t("officing.final_recounts.new.booth") %>
+ <%= select_tag :officer_assignment_id,
+ booths_for_officer_select_options(@officer_assignments),
+ { prompt: t("officing.final_recounts.new.select_booth"),
+ label: false } %>
+
+
+
+
+
+ <%= t("officing.final_recounts.new.date") %>
+ <%= select_tag :date,
+ poll_dates_select_options(@poll),
+ { prompt: t("officing.final_recounts.new.select_date"),
+ label: false } %>
+
+
+
+
+
+ <%= t("officing.final_recounts.new.count") %>
+ <%= 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") %>
+
+
+
+ <%= t("officing.final_recounts.new.date") %>
+ <%= t("officing.final_recounts.new.booth") %>
+ <%= t("officing.final_recounts.new.count") %>
+
+
+ <% @final_recounts.each do |final_recount| %>
+
+
+ <%= l(final_recount.date.to_date, format: :long) %>
+
+
+ <%= final_recount.booth_assignment.booth.name %>
+
+
+ <%= final_recount.count %>
+
+
+ <% end %>
+
+
+<% 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? %>
+
+
+ <%= t("officing.polls.final.select_poll") %>
+
+
+
+ <% @polls.each do |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" %>
+
+
+ <% end %>
+
+
+<% 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