Adds Ballot Sheet model and business logic

This commit is contained in:
María Checa
2018-06-21 22:34:54 +02:00
committed by Javi Martín
parent ccf8d3a8e2
commit 394177213c
18 changed files with 294 additions and 115 deletions

View File

@@ -19,13 +19,13 @@ class Officing::BallotSheetsController < Officing::BaseController
def create def create
load_officer_assignment load_officer_assignment
check_officer_assignment
@ballot_sheet = Poll::BallotSheet.new(ballot_sheet_params) @ballot_sheet = Poll::BallotSheet.new(ballot_sheet_params)
if @ballot_sheet.save if @ballot_sheet.save
redirect_to officing_poll_ballot_sheet_path(@poll, @ballot_sheet) redirect_to officing_poll_ballot_sheet_path(@poll, @ballot_sheet)
else else
flash.now[:alert] = @ballot_sheet.errors.full_messages.join(", ")
render :new render :new
end end
end end
@@ -63,13 +63,6 @@ class Officing::BallotSheetsController < Officing::BaseController
.find_by(id: ballot_sheet_params[:officer_assignment_id]) .find_by(id: ballot_sheet_params[:officer_assignment_id])
end end
def check_officer_assignment
if @officer_assignment.blank?
flash.now[:alert] = t("officing.results.flash.error_wrong_booth")
render :new
end
end
def ballot_sheet_params def ballot_sheet_params
params.permit(:data, :poll_id, :officer_assignment_id) params.permit(:data, :poll_id, :officer_assignment_id)
end end

View File

@@ -1,61 +0,0 @@
class Officing::PollBallotSheetsController < Officing::BaseController
before_action :verify_booth
before_action :load_poll
before_action :load_ballot_sheets, only: :index
before_action :load_ballot_sheet, only: :show
before_action :load_officer_assignments, only: :new
before_action :load_officer_assignment, only: :create
before_action :check_officer_assignment, only: :create
helper_method :namespace
def index
end
def show
end
def new
end
def create
Poll::BallotSheet.create(ballot_sheet_params)
render :show
end
private
def namespace
"officing"
end
def load_poll
@poll = Poll.find(params[:poll_id])
end
def load_ballot_sheets
@ballot_sheets = Poll::BallotSheet.where(poll: @poll)
end
def load_ballot_sheet
@ballot_sheet = Poll::BallotSheet.find(params[:ballot_sheet_id])
end
def load_officer_assignments
@officer_assignments = ::Poll::OfficerAssignment.
includes(booth_assignment: [:booth]).
joins(:booth_assignment).
final.
where(id: current_user.poll_officer.officer_assignment_ids).
where("poll_booth_assignments.poll_id = ?", @poll_budget.id).
where(date: Date.current)
end
def ballot_sheet_params
params.permit(:csv_data, :poll_id, :officer_assignment_id)
end
end

View File

@@ -20,6 +20,7 @@ class Poll < ActiveRecord::Base
has_many :officers, through: :officer_assignments has_many :officers, through: :officer_assignments
has_many :questions has_many :questions
has_many :comments, as: :commentable has_many :comments, as: :commentable
has_many :ballot_sheets
has_and_belongs_to_many :geozones has_and_belongs_to_many :geozones
belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id" belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id"

View File

@@ -0,0 +1,12 @@
class Poll::BallotSheet < ActiveRecord::Base
belongs_to :poll
belongs_to :officer_assignment
validates :data, presence: true
validates :poll_id, presence: true
validates :officer_assignment_id, presence: true
def author
officer_assignment.officer.name
end
end

View File

@@ -2,6 +2,7 @@ class Poll
class OfficerAssignment < ActiveRecord::Base class OfficerAssignment < ActiveRecord::Base
belongs_to :officer belongs_to :officer
belongs_to :booth_assignment belongs_to :booth_assignment
has_many :ballot_sheets
has_many :partial_results has_many :partial_results
has_many :recounts has_many :recounts
has_many :voters has_many :voters

View File

@@ -12,7 +12,7 @@
<% end %> <% end %>
<% if final_recount_shift? %> <% if final_recount_shift? %>
<li <%= "class=is-active" if ["results"].include?(controller_name) || (controller_name == "polls" && action_name == "final") %>> <li <%= "class=is-active" if ["results", "ballot_sheets"].include?(controller_name) || (controller_name == "polls" && action_name == "final") %>>
<%= link_to final_officing_polls_path do %> <%= link_to final_officing_polls_path do %>
<span class="icon-user"></span> <span class="icon-user"></span>
<%= t("officing.menu.total_recounts") %> <%= t("officing.menu.total_recounts") %>

View File

@@ -0,0 +1,27 @@
<h2><%= t("officing.poll_budgets.index.title", poll_budget: @poll.name) %></h2>
<% if @ballot_sheets.any? %>
<table class="fixed">
<thead>
<tr>
<th><%= t("officing.poll_budgets.index.ballot_sheet_name") %></th>
<th><%= t("officing.poll_budgets.index.ballot_sheet_author") %></th>
<th><%= t("officing.poll_budgets.index.ballot_sheet_creation_date") %></th>
</tr>
</thead>
<tbody>
<% @ballot_sheets.each do |ballot_sheet| %>
<tr id="<%= dom_id(ballot_sheet) %>" class="ballot_sheet">
<td><%= link_to t("officing.poll_budgets.index.ballot_sheet", id: ballot_sheet.id),
officing_poll_ballot_sheet_path(@poll.id, ballot_sheet.id) %></td>
<td><%= ballot_sheet.author %></td>
<td><%= l(ballot_sheet.created_at, format: :long) %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="callout primary">
<%= t("officing.poll_budgets.index.empty_results") %>
</div>
<% end %>

View File

@@ -1,15 +1,14 @@
<% if @officer_assignments.any? %> <% if @officer_assignments.any? %>
<h2><%= t("officing.poll_budgets.new.title", poll: @poll.name) %></h2> <h2><%= t("officing.poll_budgets.new.title", poll_budget: @poll.name) %></h2>
<%= form_tag(officing_poll_ballot_sheets_path(@poll)) do %> <%= form_tag(officing_poll_ballot_sheets_path(@poll)) do %>
<label><%= t("officing.poll_budgets.new.booth") %></label> <label><%= t("officing.poll_budgets.new.booth") %></label>
<%= select_tag :officer_assignment_id, <%= select_tag :officer_assignment_id,
booths_for_officer_select_options(@officer_assignments), booths_for_officer_select_options(@officer_assignments),
{ prompt: t("officing.poll_budgets.new.select_booth"), { prompt: t("officing.poll_budgets.new.select_booth"), label: false } %>
label: false } %>
<label><%= t("officing.poll_budgets.new.csv_data") %></label> <label><%= t("officing.poll_budgets.new.csv_data") %></label>
<%= text_area_tag :csv_data, nil, rows: 10 %> <%= text_area_tag :data, nil, rows: 10 %>
<%= submit_tag t("officing.poll_budgets.new.submit"), class: "button" %> <%= submit_tag t("officing.poll_budgets.new.submit"), class: "button" %>
<% end %> <% end %>

View File

@@ -0,0 +1,14 @@
<h2 class="inline-block"><%= t("officing.poll_budgets.index.ballot_sheet", id: @ballot_sheet.id) %></h2>
<div class="callout secondary float-right">
<%= t("officing.poll_budgets.show.created_at") %>
<strong><%= l(@ballot_sheet.created_at, format: :long) %></strong>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<%= t("officing.poll_budgets.show.author") %>
<strong><%= @ballot_sheet.author %></strong>
</div>
<div class="callout margin-top">
<p><strong><%= t("officing.poll_budgets.show.data") %></strong></p>
<%= simple_format @ballot_sheet.data %>
</div>

View File

@@ -1,26 +0,0 @@
<h2><%= t("officing.poll_budgets.index.title", poll_budget: @poll_budget) %></h2>
<% if @ballot_sheets.any? %>
<table class="fixed">
<thead>
<tr>
<th colspan="2"><%= t("officing.poll_budgets.index.ballot_sheet_name") %></th>
<th colspan="2"><%= t("officing.poll_budgets.index.ballot_sheet_author") %></th>
<th colspan="2"><%= t("officing.poll_budgets.index.ballot_sheet_creation_date") %></th>
</tr>
</thead>
<tbody>
<% @ballot_sheets.each do |ballot_sheet| %>
<tr id="<%= dom_id(ballot_sheet) %>" class="ballot_sheet">
<td></td>
<td></td>
<td></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="callout primary">
<%= t("officing.poll_budgets.index.empty_results") %>
</div>
<% end %>

View File

@@ -20,6 +20,7 @@ en:
see_ballot_sheets: See ballot sheets list see_ballot_sheets: See ballot sheets list
index: index:
title: "%{poll_budget} - Ballot sheets list" title: "%{poll_budget} - Ballot sheets list"
ballot_sheet: "Ballot sheet %{id}"
ballot_sheet_name: Name ballot_sheet_name: Name
ballot_sheet_author: Author ballot_sheet_author: Author
ballot_sheet_creation_date: Creation date ballot_sheet_creation_date: Creation date
@@ -30,6 +31,10 @@ en:
select_booth: Select booth select_booth: Select booth
csv_data: CSV data csv_data: CSV data
submit: Save submit: Save
show:
created_at: Creation date
author: Author
data: CSV data
booth: booth:
new: new:
title: "Choose your booth" title: "Choose your booth"

View File

@@ -20,6 +20,7 @@ es:
see_ballot_sheets: Ver lista de papeletas de votación see_ballot_sheets: Ver lista de papeletas de votación
index: index:
title: "%{poll_budget} - Listado de papeletas de votación" title: "%{poll_budget} - Listado de papeletas de votación"
ballot_sheet: "Papeleta de votación %{id}"
ballot_sheet_name: Nombre ballot_sheet_name: Nombre
ballot_sheet_author: Autor ballot_sheet_author: Autor
ballot_sheet_creation_date: Fecha de creación ballot_sheet_creation_date: Fecha de creación
@@ -30,6 +31,10 @@ es:
select_booth: Elegir urna select_booth: Elegir urna
csv_data: Datos de CSV csv_data: Datos de CSV
submit: Guardar submit: Guardar
show:
created_at: Fecha de creación
author: Autor
data: Datos de CSV
booth: booth:
new: new:
title: "Escoge tu urna" title: "Escoge tu urna"

View File

@@ -0,0 +1,10 @@
class CreatePollBallotSheets < ActiveRecord::Migration
def change
create_table :poll_ballot_sheets do |t|
t.text :data
t.integer :poll_id, index: true
t.integer :officer_assignment_id, index: true
t.timestamps null: false
end
end
end

View File

@@ -927,6 +927,17 @@ ActiveRecord::Schema.define(version: 20190205131722) do
add_index "poll_answers", ["question_id", "answer"], name: "index_poll_answers_on_question_id_and_answer", using: :btree add_index "poll_answers", ["question_id", "answer"], name: "index_poll_answers_on_question_id_and_answer", using: :btree
add_index "poll_answers", ["question_id"], name: "index_poll_answers_on_question_id", using: :btree add_index "poll_answers", ["question_id"], name: "index_poll_answers_on_question_id", using: :btree
create_table "poll_ballot_sheets", force: :cascade do |t|
t.text "data"
t.integer "poll_id"
t.integer "officer_assignment_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "poll_ballot_sheets", ["officer_assignment_id"], name: "index_poll_ballot_sheets_on_officer_assignment_id", using: :btree
add_index "poll_ballot_sheets", ["poll_id"], name: "index_poll_ballot_sheets_on_poll_id", using: :btree
create_table "poll_booth_assignments", force: :cascade do |t| create_table "poll_booth_assignments", force: :cascade do |t|
t.integer "booth_id" t.integer "booth_id"
t.integer "poll_id" t.integer "poll_id"

View File

@@ -147,6 +147,12 @@ FactoryBot.define do
end end
end end
factory :poll_ballot_sheet, class: "Poll::BallotSheet" do
association :poll
association :officer_assignment, factory: :poll_officer_assignment
data "1234;9876;5678\n1000;2000;3000;9999"
end
factory :officing_residence, class: "Officing::Residence" do factory :officing_residence, class: "Officing::Residence" do
user user
association :officer, factory: :poll_officer association :officer, factory: :poll_officer

View File

@@ -6,27 +6,169 @@ feature "Poll budget ballot sheets" do
let(:booth) { create(:poll_booth) } let(:booth) { create(:poll_booth) }
let(:poll_officer) { create(:poll_officer) } let(:poll_officer) { create(:poll_officer) }
background do context "Officing recounts and results view" do
create(:poll_booth_assignment, poll: poll, booth: booth)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth, date: Date.current) background do
create(:poll_officer_assignment, officer: poll_officer) create(:poll_booth_assignment, poll: poll, booth: booth)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth,
date: Date.current)
create(:poll_officer_assignment, officer: poll_officer)
login_as(poll_officer.user)
set_officing_booth(booth)
end
scenario "Budget polls are visible" do
visit root_path
click_link "Polling officers"
within("#side_menu") do
click_link "Total recounts and results"
end
within("#poll_#{poll.id}") do
expect(page).to have_content("#{poll.name}")
expect(page).to have_content("See ballot sheets list")
expect(page).to have_content("Add results")
end
end
login_as(poll_officer.user)
set_officing_booth(booth)
end end
scenario "Budget polls are visible in 'Recounts and results' view" do context "Booth assignment" do
visit root_path
click_link "Polling officers"
within("#side_menu") do scenario "Try to access ballot sheets officing without booth assignment" do
click_link "Total recounts and results" login_as(poll_officer.user)
visit officing_poll_ballot_sheets_path(poll)
expect(page).to have_content "You don't have officing shifts today"
end end
within("#poll_#{poll.id}") do scenario "Access ballot sheets officing with one booth assignment" do
expect(page).to have_content("#{poll.name}") create(:poll_booth_assignment, poll: poll, booth: booth)
expect(page).to have_content("See ballot sheets list") create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth,
expect(page).to have_content("Add results") date: Date.current)
create(:poll_officer_assignment, officer: poll_officer)
login_as(poll_officer.user)
set_officing_booth(booth)
visit officing_poll_ballot_sheets_path(poll)
expect(page).to have_content "#{poll.name}"
end end
scenario "Access ballot sheets officing with multiple booth assignments", :with_frozen_time do
booth_2 = create(:poll_booth)
create(:poll_booth_assignment, poll: poll, booth: booth)
create(:poll_booth_assignment, poll: poll, booth: booth_2)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth,
date: Date.current)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth_2,
date: Date.current)
create(:poll_officer_assignment, officer: poll_officer)
create(:poll_officer_assignment, officer: poll_officer)
login_as(poll_officer.user)
visit officing_poll_ballot_sheets_path(poll)
expect(page).to have_content "Choose your booth"
end
end
context "Index" do
background do
create(:poll_booth_assignment, poll: poll, booth: booth)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth,
date: Date.current)
login_as(poll_officer.user)
set_officing_booth(booth)
end
scenario "Ballot sheets are listed" do
officer_assignment = create(:poll_officer_assignment, officer: poll_officer)
ballot_sheet = create(:poll_ballot_sheet, poll: poll, officer_assignment: officer_assignment)
visit officing_poll_ballot_sheets_path(poll)
expect(page).to have_content "Ballot sheet #{ballot_sheet.id}"
end
end
context "New" do
background do
create(:poll_booth_assignment, poll: poll, booth: booth)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth,
date: Date.current)
create(:poll_officer_assignment, officer: poll_officer)
login_as(poll_officer.user)
set_officing_booth(booth)
end
scenario "Ballot sheet is saved" do
visit new_officing_poll_ballot_sheet_path(poll)
select "#{booth.name}", from: "officer_assignment_id"
fill_in "data", with: "1234;5678"
click_button "Save"
expect(Poll::BallotSheet.count).to be 1
expect(page).to have_content("Ballot sheet #{Poll::BallotSheet.last.id}")
expect(page).to have_content(poll_officer.user.name)
expect(page).to have_content("1234;5678")
end
scenario "Ballot sheet is not saved" do
visit new_officing_poll_ballot_sheet_path(poll)
select "#{booth.name}", from: "officer_assignment_id"
click_button "Save"
expect(Poll::BallotSheet.count).to be 0
expect(page).to have_content("CSV data can't be blank")
end
scenario "Shift booth has to be selected", :js do
visit new_officing_poll_ballot_sheet_path(poll)
fill_in "data", with: "1234;5678"
click_button "Save"
expect(page).to have_content "Officer assignment can't be blank"
end
end
context "Show" do
background do
create(:poll_booth_assignment, poll: poll, booth: booth)
create(:poll_shift, :recount_scrutiny_task, officer: poll_officer, booth: booth,
date: Date.current)
login_as(poll_officer.user)
set_officing_booth(booth)
end
scenario "Ballot sheet information is displayed" do
officer_assignment = create(:poll_officer_assignment, officer: poll_officer)
ballot_sheet = create(:poll_ballot_sheet, poll: poll, officer_assignment: officer_assignment)
visit officing_poll_ballot_sheet_path(poll, ballot_sheet)
expect(page).to have_content("Ballot sheet #{ballot_sheet.id}")
expect(page).to have_content(ballot_sheet.author)
expect(page).to have_content(ballot_sheet.data)
end
end end
end end

View File

@@ -0,0 +1,40 @@
require "rails_helper"
describe Poll::BallotSheet do
let(:ballot_sheet) { build(:poll_ballot_sheet, poll: create(:poll),
officer_assignment: create(:poll_officer_assignment),
data: "1234;5678") }
context "Validations" do
it "is valid" do
expect(ballot_sheet).to be_valid
end
it "is not valid without a poll" do
ballot_sheet.poll = nil
expect(ballot_sheet).not_to be_valid
end
it "is not valid without an officer assignment" do
ballot_sheet.officer_assignment = nil
expect(ballot_sheet).not_to be_valid
end
it "is not valid without data" do
ballot_sheet.data = nil
expect(ballot_sheet).not_to be_valid
end
end
context "#author" do
it "returns the officer's name" do
expect(ballot_sheet.author).to be(ballot_sheet.officer_assignment.officer.user.name)
end
end
end