From 394177213cf15c29df52bf6f46797a04e1b59d93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Checa?= Date: Thu, 21 Jun 2018 22:34:54 +0200 Subject: [PATCH] Adds Ballot Sheet model and business logic --- .../officing/ballot_sheets_controller.rb | 9 +- .../officing/poll/ballot_sheets_controller.rb | 61 ------- app/models/poll.rb | 1 + app/models/poll/ballot_sheet.rb | 12 ++ app/models/poll/officer_assignment.rb | 1 + app/views/officing/_menu.html.erb | 2 +- .../officing/ballot_sheets/index.html.erb | 27 +++ .../{polls => }/ballot_sheets/new.html.erb | 7 +- .../officing/ballot_sheets/show.html.erb | 14 ++ .../polls/ballot_sheets/index.html.erb | 26 --- .../polls/ballot_sheets/show.html.erb | 0 config/locales/en/officing.yml | 5 + config/locales/es/officing.yml | 5 + ...0180621182723_create_poll_ballot_sheets.rb | 10 + db/schema.rb | 11 ++ spec/factories/polls.rb | 6 + .../budget_polls/ballot_sheets_spec.rb | 172 ++++++++++++++++-- spec/models/poll/ballot_sheet_spec.rb | 40 ++++ 18 files changed, 294 insertions(+), 115 deletions(-) delete mode 100644 app/controllers/officing/poll/ballot_sheets_controller.rb create mode 100644 app/models/poll/ballot_sheet.rb create mode 100644 app/views/officing/ballot_sheets/index.html.erb rename app/views/officing/{polls => }/ballot_sheets/new.html.erb (74%) create mode 100644 app/views/officing/ballot_sheets/show.html.erb delete mode 100644 app/views/officing/polls/ballot_sheets/index.html.erb delete mode 100644 app/views/officing/polls/ballot_sheets/show.html.erb create mode 100644 db/migrate/20180621182723_create_poll_ballot_sheets.rb create mode 100644 spec/models/poll/ballot_sheet_spec.rb diff --git a/app/controllers/officing/ballot_sheets_controller.rb b/app/controllers/officing/ballot_sheets_controller.rb index e23ea4613..9a4f08401 100644 --- a/app/controllers/officing/ballot_sheets_controller.rb +++ b/app/controllers/officing/ballot_sheets_controller.rb @@ -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 diff --git a/app/controllers/officing/poll/ballot_sheets_controller.rb b/app/controllers/officing/poll/ballot_sheets_controller.rb deleted file mode 100644 index 344be299f..000000000 --- a/app/controllers/officing/poll/ballot_sheets_controller.rb +++ /dev/null @@ -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 diff --git a/app/models/poll.rb b/app/models/poll.rb index 553baf036..012fce912 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -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" diff --git a/app/models/poll/ballot_sheet.rb b/app/models/poll/ballot_sheet.rb new file mode 100644 index 000000000..24c3c38b8 --- /dev/null +++ b/app/models/poll/ballot_sheet.rb @@ -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 diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb index 7de4f98b3..c997c9a5a 100644 --- a/app/models/poll/officer_assignment.rb +++ b/app/models/poll/officer_assignment.rb @@ -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 diff --git a/app/views/officing/_menu.html.erb b/app/views/officing/_menu.html.erb index 17b57a087..5630702c0 100644 --- a/app/views/officing/_menu.html.erb +++ b/app/views/officing/_menu.html.erb @@ -12,7 +12,7 @@ <% end %> <% if final_recount_shift? %> -
  • > +
  • > <%= link_to final_officing_polls_path do %> <%= t("officing.menu.total_recounts") %> diff --git a/app/views/officing/ballot_sheets/index.html.erb b/app/views/officing/ballot_sheets/index.html.erb new file mode 100644 index 000000000..ce1cd7db1 --- /dev/null +++ b/app/views/officing/ballot_sheets/index.html.erb @@ -0,0 +1,27 @@ +

    <%= t("officing.poll_budgets.index.title", poll_budget: @poll.name) %>

    + +<% if @ballot_sheets.any? %> + + + + + + + + + + <% @ballot_sheets.each do |ballot_sheet| %> + + + + + + <% end %> + +
    <%= t("officing.poll_budgets.index.ballot_sheet_name") %><%= t("officing.poll_budgets.index.ballot_sheet_author") %><%= t("officing.poll_budgets.index.ballot_sheet_creation_date") %>
    <%= link_to t("officing.poll_budgets.index.ballot_sheet", id: ballot_sheet.id), + officing_poll_ballot_sheet_path(@poll.id, ballot_sheet.id) %><%= ballot_sheet.author %><%= l(ballot_sheet.created_at, format: :long) %>
    +<% else %> +
    + <%= t("officing.poll_budgets.index.empty_results") %> +
    +<% end %> diff --git a/app/views/officing/polls/ballot_sheets/new.html.erb b/app/views/officing/ballot_sheets/new.html.erb similarity index 74% rename from app/views/officing/polls/ballot_sheets/new.html.erb rename to app/views/officing/ballot_sheets/new.html.erb index 335477ec3..a83c3a7d0 100644 --- a/app/views/officing/polls/ballot_sheets/new.html.erb +++ b/app/views/officing/ballot_sheets/new.html.erb @@ -1,15 +1,14 @@ <% if @officer_assignments.any? %> -

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

    +

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

    <%= form_tag(officing_poll_ballot_sheets_path(@poll)) do %> <%= 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 } %> - <%= 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 %> diff --git a/app/views/officing/ballot_sheets/show.html.erb b/app/views/officing/ballot_sheets/show.html.erb new file mode 100644 index 000000000..6a111f353 --- /dev/null +++ b/app/views/officing/ballot_sheets/show.html.erb @@ -0,0 +1,14 @@ +

    <%= t("officing.poll_budgets.index.ballot_sheet", id: @ballot_sheet.id) %>

    + +
    + <%= t("officing.poll_budgets.show.created_at") %> + <%= l(@ballot_sheet.created_at, format: :long) %> +  •  + <%= t("officing.poll_budgets.show.author") %> + <%= @ballot_sheet.author %> +
    + +
    +

    <%= t("officing.poll_budgets.show.data") %>

    + <%= simple_format @ballot_sheet.data %> +
    diff --git a/app/views/officing/polls/ballot_sheets/index.html.erb b/app/views/officing/polls/ballot_sheets/index.html.erb deleted file mode 100644 index 2dc905b4f..000000000 --- a/app/views/officing/polls/ballot_sheets/index.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -

    <%= t("officing.poll_budgets.index.title", poll_budget: @poll_budget) %>

    - -<% if @ballot_sheets.any? %> - - - - - - - - - - <% @ballot_sheets.each do |ballot_sheet| %> - - - - - - <% end %> - -
    <%= t("officing.poll_budgets.index.ballot_sheet_name") %><%= t("officing.poll_budgets.index.ballot_sheet_author") %><%= t("officing.poll_budgets.index.ballot_sheet_creation_date") %>
    -<% else %> -
    - <%= t("officing.poll_budgets.index.empty_results") %> -
    -<% end %> diff --git a/app/views/officing/polls/ballot_sheets/show.html.erb b/app/views/officing/polls/ballot_sheets/show.html.erb deleted file mode 100644 index e69de29bb..000000000 diff --git a/config/locales/en/officing.yml b/config/locales/en/officing.yml index b40f7c448..91b1aeb4e 100644 --- a/config/locales/en/officing.yml +++ b/config/locales/en/officing.yml @@ -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" diff --git a/config/locales/es/officing.yml b/config/locales/es/officing.yml index 103a25982..94f75ae25 100644 --- a/config/locales/es/officing.yml +++ b/config/locales/es/officing.yml @@ -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" diff --git a/db/migrate/20180621182723_create_poll_ballot_sheets.rb b/db/migrate/20180621182723_create_poll_ballot_sheets.rb new file mode 100644 index 000000000..ee06c5724 --- /dev/null +++ b/db/migrate/20180621182723_create_poll_ballot_sheets.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 96e3d3487..d65c89471 100644 --- a/db/schema.rb +++ b/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" diff --git a/spec/factories/polls.rb b/spec/factories/polls.rb index cf62728d2..895d723a9 100644 --- a/spec/factories/polls.rb +++ b/spec/factories/polls.rb @@ -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 diff --git a/spec/features/budget_polls/ballot_sheets_spec.rb b/spec/features/budget_polls/ballot_sheets_spec.rb index a20921126..cc42e253d 100644 --- a/spec/features/budget_polls/ballot_sheets_spec.rb +++ b/spec/features/budget_polls/ballot_sheets_spec.rb @@ -6,27 +6,169 @@ feature "Poll budget ballot sheets" do let(:booth) { create(:poll_booth) } let(:poll_officer) { create(:poll_officer) } - 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) + 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_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 - scenario "Budget polls are visible in 'Recounts and results' view" do - visit root_path - click_link "Polling officers" + context "Booth assignment" do - within("#side_menu") do - click_link "Total recounts and results" + 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 - 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") + 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 diff --git a/spec/models/poll/ballot_sheet_spec.rb b/spec/models/poll/ballot_sheet_spec.rb new file mode 100644 index 000000000..2ad0e0d5d --- /dev/null +++ b/spec/models/poll/ballot_sheet_spec.rb @@ -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