Adds Ballot Sheet model and business logic
This commit is contained in:
@@ -19,13 +19,13 @@ class Officing::BallotSheetsController < Officing::BaseController
|
||||
|
||||
def create
|
||||
load_officer_assignment
|
||||
check_officer_assignment
|
||||
|
||||
@ballot_sheet = Poll::BallotSheet.new(ballot_sheet_params)
|
||||
|
||||
if @ballot_sheet.save
|
||||
redirect_to officing_poll_ballot_sheet_path(@poll, @ballot_sheet)
|
||||
else
|
||||
flash.now[:alert] = @ballot_sheet.errors.full_messages.join(", ")
|
||||
render :new
|
||||
end
|
||||
end
|
||||
@@ -63,13 +63,6 @@ class Officing::BallotSheetsController < Officing::BaseController
|
||||
.find_by(id: ballot_sheet_params[:officer_assignment_id])
|
||||
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
|
||||
params.permit(:data, :poll_id, :officer_assignment_id)
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -20,6 +20,7 @@ class Poll < ActiveRecord::Base
|
||||
has_many :officers, through: :officer_assignments
|
||||
has_many :questions
|
||||
has_many :comments, as: :commentable
|
||||
has_many :ballot_sheets
|
||||
|
||||
has_and_belongs_to_many :geozones
|
||||
belongs_to :author, -> { with_hidden }, class_name: "User", foreign_key: "author_id"
|
||||
|
||||
12
app/models/poll/ballot_sheet.rb
Normal file
12
app/models/poll/ballot_sheet.rb
Normal 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
|
||||
@@ -2,6 +2,7 @@ class Poll
|
||||
class OfficerAssignment < ActiveRecord::Base
|
||||
belongs_to :officer
|
||||
belongs_to :booth_assignment
|
||||
has_many :ballot_sheets
|
||||
has_many :partial_results
|
||||
has_many :recounts
|
||||
has_many :voters
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<% end %>
|
||||
|
||||
<% 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 %>
|
||||
<span class="icon-user"></span>
|
||||
<%= t("officing.menu.total_recounts") %>
|
||||
|
||||
27
app/views/officing/ballot_sheets/index.html.erb
Normal file
27
app/views/officing/ballot_sheets/index.html.erb
Normal 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 %>
|
||||
@@ -1,15 +1,14 @@
|
||||
<% 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 %>
|
||||
<label><%= t("officing.poll_budgets.new.booth") %></label>
|
||||
<%= select_tag :officer_assignment_id,
|
||||
booths_for_officer_select_options(@officer_assignments),
|
||||
{ prompt: t("officing.poll_budgets.new.select_booth"),
|
||||
label: false } %>
|
||||
{ prompt: t("officing.poll_budgets.new.select_booth"), label: false } %>
|
||||
|
||||
<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" %>
|
||||
<% end %>
|
||||
14
app/views/officing/ballot_sheets/show.html.erb
Normal file
14
app/views/officing/ballot_sheets/show.html.erb
Normal 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"> • </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>
|
||||
@@ -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 %>
|
||||
@@ -20,6 +20,7 @@ en:
|
||||
see_ballot_sheets: See ballot sheets list
|
||||
index:
|
||||
title: "%{poll_budget} - Ballot sheets list"
|
||||
ballot_sheet: "Ballot sheet %{id}"
|
||||
ballot_sheet_name: Name
|
||||
ballot_sheet_author: Author
|
||||
ballot_sheet_creation_date: Creation date
|
||||
@@ -30,6 +31,10 @@ en:
|
||||
select_booth: Select booth
|
||||
csv_data: CSV data
|
||||
submit: Save
|
||||
show:
|
||||
created_at: Creation date
|
||||
author: Author
|
||||
data: CSV data
|
||||
booth:
|
||||
new:
|
||||
title: "Choose your booth"
|
||||
|
||||
@@ -20,6 +20,7 @@ es:
|
||||
see_ballot_sheets: Ver lista de papeletas de votación
|
||||
index:
|
||||
title: "%{poll_budget} - Listado de papeletas de votación"
|
||||
ballot_sheet: "Papeleta de votación %{id}"
|
||||
ballot_sheet_name: Nombre
|
||||
ballot_sheet_author: Autor
|
||||
ballot_sheet_creation_date: Fecha de creación
|
||||
@@ -30,6 +31,10 @@ es:
|
||||
select_booth: Elegir urna
|
||||
csv_data: Datos de CSV
|
||||
submit: Guardar
|
||||
show:
|
||||
created_at: Fecha de creación
|
||||
author: Autor
|
||||
data: Datos de CSV
|
||||
booth:
|
||||
new:
|
||||
title: "Escoge tu urna"
|
||||
|
||||
10
db/migrate/20180621182723_create_poll_ballot_sheets.rb
Normal file
10
db/migrate/20180621182723_create_poll_ballot_sheets.rb
Normal 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
|
||||
11
db/schema.rb
11
db/schema.rb
@@ -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"], 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|
|
||||
t.integer "booth_id"
|
||||
t.integer "poll_id"
|
||||
|
||||
@@ -147,6 +147,12 @@ FactoryBot.define do
|
||||
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
|
||||
user
|
||||
association :officer, factory: :poll_officer
|
||||
|
||||
@@ -6,16 +6,19 @@ feature "Poll budget ballot sheets" do
|
||||
let(:booth) { create(:poll_booth) }
|
||||
let(:poll_officer) { create(:poll_officer) }
|
||||
|
||||
context "Officing recounts and results view" 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_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 in 'Recounts and results' view" do
|
||||
scenario "Budget polls are visible" do
|
||||
visit root_path
|
||||
click_link "Polling officers"
|
||||
|
||||
@@ -29,4 +32,143 @@ feature "Poll budget ballot sheets" do
|
||||
expect(page).to have_content("Add results")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "Booth assignment" do
|
||||
|
||||
scenario "Try to access ballot sheets officing without booth assignment" do
|
||||
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
|
||||
|
||||
scenario "Access ballot sheets officing with one booth assignment" 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)
|
||||
|
||||
visit officing_poll_ballot_sheets_path(poll)
|
||||
|
||||
expect(page).to have_content "#{poll.name}"
|
||||
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
|
||||
|
||||
40
spec/models/poll/ballot_sheet_spec.rb
Normal file
40
spec/models/poll/ballot_sheet_spec.rb
Normal 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
|
||||
Reference in New Issue
Block a user