diff --git a/Gemfile b/Gemfile index 1294f4e9..b22ff4ea 100644 --- a/Gemfile +++ b/Gemfile @@ -61,6 +61,7 @@ gem 'foodsoft_wiki', path: 'plugins/wiki' gem 'foodsoft_messages', path: 'plugins/messages' gem 'foodsoft_documents', path: 'plugins/documents' gem 'foodsoft_discourse', path: 'plugins/discourse' +gem 'foodsoft_polls', path: 'plugins/polls' # plugins not enabled by default #gem 'foodsoft_current_orders', path: 'plugins/current_orders' diff --git a/Gemfile.lock b/Gemfile.lock index 97ce8696..236adc79 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,6 +36,13 @@ PATH mail rails +PATH + remote: plugins/polls + specs: + foodsoft_polls (0.0.1) + deface (~> 1.0) + rails + PATH remote: plugins/wiki specs: @@ -528,6 +535,7 @@ DEPENDENCIES foodsoft_discourse! foodsoft_documents! foodsoft_messages! + foodsoft_polls! foodsoft_wiki! gaffe haml (~> 4.0) diff --git a/app/models/user.rb b/app/models/user.rb index 072e2367..4a1453d0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -210,6 +210,12 @@ class User < ApplicationRecord end end + def self.custom_fields + fields = FoodsoftConfig[:custom_fields] && FoodsoftConfig[:custom_fields][:user] + return [] unless fields + fields.map(&:deep_symbolize_keys) + end + # XXX this is view-related; need to move out things like token_attributes # then this can be removed def display diff --git a/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb b/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb new file mode 100644 index 00000000..f45be067 --- /dev/null +++ b/db/migrate/20181110000000_create_polls.foodsoft_polls_engine.rb @@ -0,0 +1,37 @@ +class CreatePolls < ActiveRecord::Migration + def change + create_table :polls do |t| + t.integer :created_by_user_id, null: false + t.string :name, null: false + t.text :description + t.datetime :starts + t.datetime :ends + t.boolean :one_vote_per_ordergroup, default: false, null: false + t.text :required_ordergroup_custom_fields + t.text :required_user_custom_fields + t.integer :voting_method, null: false + t.string :choices, array: true, null: false + t.integer :final_choice, index: true + t.integer :multi_select_count, default: 0, null: false + t.integer :min_points + t.integer :max_points + t.timestamps + end + + create_table :poll_votes do |t| + t.references :poll, null: false + t.references :user, null: false + t.references :ordergroup + t.text :note + t.timestamps + t.index [:poll_id, :user_id, :ordergroup_id], unique: true + end + + create_table :poll_choices do |t| + t.references :poll_vote, null: false + t.integer :choice, null: false + t.integer :value, null: false + t.index [:poll_vote_id, :choice], unique: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 9bd1946d..c4dd2045 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -389,6 +389,46 @@ ActiveRecord::Schema.define(version: 20181201000200) do t.datetime "updated_at", null: false end + create_table "poll_choices", force: :cascade do |t| + t.integer "poll_vote_id", limit: 4, null: false + t.integer "choice", limit: 4, null: false + t.integer "value", limit: 4, null: false + end + + add_index "poll_choices", ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true, using: :btree + + create_table "poll_votes", force: :cascade do |t| + t.integer "poll_id", limit: 4, null: false + t.integer "user_id", limit: 4, null: false + t.integer "ordergroup_id", limit: 4 + t.text "note", limit: 65535 + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "poll_votes", ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true, using: :btree + + create_table "polls", force: :cascade do |t| + t.integer "created_by_user_id", limit: 4, null: false + t.string "name", limit: 255, null: false + t.text "description", limit: 65535 + t.datetime "starts" + t.datetime "ends" + t.boolean "one_vote_per_ordergroup", default: false, null: false + t.text "required_ordergroup_custom_fields", limit: 65535 + t.text "required_user_custom_fields", limit: 65535 + t.integer "voting_method", limit: 4, null: false + t.string "choices", limit: 255, null: false + t.integer "final_choice", limit: 4 + t.integer "multi_select_count", limit: 4, default: 0, null: false + t.integer "min_points", limit: 4 + t.integer "max_points", limit: 4 + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "polls", ["final_choice"], name: "index_polls_on_final_choice", using: :btree + create_table "printer_job_updates", force: :cascade do |t| t.integer "printer_job_id", limit: 4, null: false t.datetime "created_at", null: false diff --git a/plugins/polls/README.md b/plugins/polls/README.md new file mode 100644 index 00000000..3ff158b2 --- /dev/null +++ b/plugins/polls/README.md @@ -0,0 +1,18 @@ +FoodsoftPolls +============= + +This plugin adds polls to foodsoft. A new 'Polls' menu entry is added below the 'Foodcoops' menu in the navigation bar. + +This plugin is enabled by default in foodsoft, so you don't need to do anything +to install it. If you still want to, for example when it has been disabled, +add the following to foodsoft's Gemfile: + +```Gemfile +gem 'foodsoft_polls', path: 'lib/foodsoft_polls' +``` + +This plugin introduces the foodcoop config option `use_polls`, which can be +set to `false` to disable polls. May be useful in multicoop deployments. + +This plugin is part of the foodsoft package and uses the AGPL-3 license (see +foodsoft's LICENSE for the full license text). diff --git a/plugins/polls/Rakefile b/plugins/polls/Rakefile new file mode 100644 index 00000000..070cb3df --- /dev/null +++ b/plugins/polls/Rakefile @@ -0,0 +1,40 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end +begin + require 'rdoc/task' +rescue LoadError + require 'rdoc/rdoc' + require 'rake/rdoctask' + RDoc::Task = Rake::RDocTask +end + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'FoodsoftDocuments' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +load 'rails/tasks/engine.rake' + + + +Bundler::GemHelper.install_tasks + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task :default => :test diff --git a/plugins/polls/app/controllers/polls_controller.rb b/plugins/polls/app/controllers/polls_controller.rb new file mode 100644 index 00000000..497ef930 --- /dev/null +++ b/plugins/polls/app/controllers/polls_controller.rb @@ -0,0 +1,108 @@ +class PollsController < ApplicationController + before_filter -> { require_plugin_enabled FoodsoftPolls } + + def index + @polls = Poll.page(params[:page]).per(@per_page).order(created_at: :desc) + end + + def show + @poll = Poll.find(params[:id]) + end + + def new + @poll = Poll.new + end + + def create + @poll = Poll.new(poll_params) + @poll.created_by = current_user + + if @poll.save + redirect_to @poll, notice: t('.notice') + else + render action: 'edit' + end + end + + def edit + @poll = Poll.find(params[:id]) + + if user_has_no_right + redirect_to polls_path, alert: t('.no_right') + end + end + + def update + @poll = Poll.find(params[:id]) + + if user_has_no_right + redirect_to polls_path, alert: t('.no_right') + elsif @poll.update_attributes(poll_params) + redirect_to @poll, notice: t('.notice') + else + render action: 'edit' + end + end + + def destroy + @poll = Poll.find(params[:id]) + + if user_has_no_right + redirect_to polls_path, alert: t('.no_right') + else + @poll.destroy + redirect_to polls_path, notice: t('.notice') + end + rescue => error + redirect_to polls_path, alert: t('.error', error: error.message) + end + + def vote + @poll = Poll.find(params[:id]) + + if @poll.one_vote_per_ordergroup + ordergroup = current_user.ordergroup + return redirect_to polls_path, alert: t('.no_ordergroup') unless ordergroup + attributes = { ordergroup: ordergroup } + else + attributes = { user: current_user } + end + + redirect_to @poll, alert: t('.no_right') unless @poll.user_can_vote?(current_user) + + @poll_vote = @poll.poll_votes.where(attributes).first_or_initialize + + if request.post? + @poll_vote.update!(note: params[:note], user: current_user) + + if @poll.single_select? + choices = { params[:choice] => '1' } + else + choices = params[:choices] || {} + end + + @poll_vote.poll_choices = choices.map do |choice, value| + poll_choice = @poll_vote.poll_choices.where(choice: choice).first_or_initialize + poll_choice.update!(value: value) + poll_choice + end + + redirect_to @poll + end + end + + private + + def user_has_no_right + @poll.created_by != current_user && !current_user.role_admin? + end + + def poll_params + params + .require(:poll) + .permit(:name, :starts_date_value, :starts_time_value, :ends_date_value, + :ends_time_value, :description, :one_vote_per_ordergroup, :voting_method, + :multi_select_count, :min_points, :max_points, choices: [], + required_ordergroup_custom_fields: [], required_user_custom_fields: []) + end +end diff --git a/plugins/polls/app/models/poll.rb b/plugins/polls/app/models/poll.rb new file mode 100644 index 00000000..ec7b1506 --- /dev/null +++ b/plugins/polls/app/models/poll.rb @@ -0,0 +1,50 @@ +class Poll < ActiveRecord::Base + + # @!attribute required_ordergroup_custom_fields + # A list of custom_fileds, which are required to poll. + # If the required field on the ordergroup of the user + # is empty the user will not be able to place a vote. + # @return [Array] Required field names. + # @!attribute required_user_custom_fields + # A list of custom_fileds, which are required to poll. + # If the required field on the user is empty the user + # will not be able to place a vote. + # @return [Array] Required field names. + + belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id' + has_many :poll_votes, dependent: :destroy + + validates_presence_of :name, :choices + serialize :choices, Array + serialize :required_ordergroup_custom_fields, Array + serialize :required_user_custom_fields, Array + enum voting_method: { event: 0, single_select: 1, multi_select: 2, points: 3, resistance_points: 4 } + + include DateTimeAttributeValidate + date_time_attribute :starts, :ends + + def available_points + return 0...0 if min_points.nil? || max_points.nil? + min_points..max_points + end + + def user_can_edit?(user) + created_by == user || user.role_admin? + end + + def user_can_vote?(user) + Ordergroup.custom_fields.each do |field| + name = field[:name] + next unless required_ordergroup_custom_fields.include? name + return false if user.ordergroup.nil? || user.ordergroup.settings.custom_fields[name].blank? + end + + User.custom_fields.each do |field| + name = field[:name] + next unless required_user_custom_fields.include? name + return false if user.settings.custom_fields[name].blank? + end + + true + end +end diff --git a/plugins/polls/app/models/poll_choice.rb b/plugins/polls/app/models/poll_choice.rb new file mode 100644 index 00000000..32e50408 --- /dev/null +++ b/plugins/polls/app/models/poll_choice.rb @@ -0,0 +1,3 @@ +class PollChoice < ActiveRecord::Base + belongs_to :poll_vote, touch: true +end diff --git a/plugins/polls/app/models/poll_vote.rb b/plugins/polls/app/models/poll_vote.rb new file mode 100644 index 00000000..f7e6f73c --- /dev/null +++ b/plugins/polls/app/models/poll_vote.rb @@ -0,0 +1,6 @@ +class PollVote < ActiveRecord::Base + belongs_to :poll + belongs_to :ordergroup + belongs_to :user + has_many :poll_choices, dependent: :destroy +end diff --git a/plugins/polls/app/overrides/admin/configs/_tab_others/add_polls_config.html.haml.deface b/plugins/polls/app/overrides/admin/configs/_tab_others/add_polls_config.html.haml.deface new file mode 100644 index 00000000..a13c92fd --- /dev/null +++ b/plugins/polls/app/overrides/admin/configs/_tab_others/add_polls_config.html.haml.deface @@ -0,0 +1,2 @@ +/ insert_before ':root:first-child' += config_input form, :use_polls, as: :boolean diff --git a/plugins/polls/app/views/polls/_choice.haml b/plugins/polls/app/views/polls/_choice.haml new file mode 100644 index 00000000..f07e8c66 --- /dev/null +++ b/plugins/polls/app/views/polls/_choice.haml @@ -0,0 +1,4 @@ +%div + = text_field_tag 'poll[choices][]', value + = link_to t('.remove'), "#", title: t('.remove_choice'), 'data-remove-choice' => true, + class: 'btn btn-small' diff --git a/plugins/polls/app/views/polls/_form.html.haml b/plugins/polls/app/views/polls/_form.html.haml new file mode 100644 index 00000000..9405a7ed --- /dev/null +++ b/plugins/polls/app/views/polls/_form.html.haml @@ -0,0 +1,76 @@ +- content_for :javascript do + :javascript + var choice = $($.parseHTML("#{escape_javascript(render(partial: 'choice', locals: {value: ''}))}")); + + $(function() { + function updateSlideState() { + switch($('select[name="poll[voting_method]"]').val()) { + case 'event': + case 'single_select': + $('#multi_select_count').slideUp(); + $('#min_max_points').slideUp(); + break; + + case 'multi_select': + $('#multi_select_count').slideDown(); + $('#min_max_points').slideUp(); + break; + + case 'points': + case 'resistance_points': + $('#multi_select_count').slideUp(); + $('#min_max_points').slideDown(); + break; + } + } + + $('select[name="poll[voting_method]"]').on('change', updateSlideState); + + $('a[data-remove-choice]').on('click', function() { + $(this).parent().remove(); + return false; + }); + + $('a[data-add-choice]').on('touchclick', function() { + choice.clone().appendTo('#choices'); + return false; + }); + + updateSlideState(); + }); + += simple_form_for @poll do |f| + = f.input :name + .fold-line + = f.input :starts, as: :date_picker_time + = f.input :ends, as: :date_picker_time + = f.input :description, input_html: {rows: 2, class: 'input-xxlarge'} + - if @poll.poll_votes.any? + = f.input :voting_method, label: false do + = f.hint t('.already_voted') + - else + = f.input :one_vote_per_ordergroup, inline_label: true, label: false + - if Ordergroup.custom_fields.any? + = f.input :required_ordergroup_custom_fields, as: :check_boxes, label: false, + collection: Ordergroup.custom_fields.map{|f| [t('.required_ordergroup_custom_field', label: f[:label]), f[:name]]}.to_h + - if User.custom_fields.any? + = f.input :required_user_custom_fields, as: :check_boxes, label: false, + collection: User.custom_fields.map{|f| [t('.required_user_custom_field', label: f[:label]), f[:name]]}.to_h + = f.input :voting_method, collection: Poll.voting_methods,include_blank: false, + input_html: {class: 'input-xxlarge'}, value_method: ->(k){ k.first }, + label_method: ->(k){ t("activerecord.attributes.poll.voting_methods.#{k.first}") } + #multi_select_count + = f.input :multi_select_count, input_html: { min: 0 } + #min_max_points + .fold-line + = f.input :min_points + = f.input :max_points + = f.input :choices do + #choices + = render partial: 'choice', collection: @poll.choices, as: :value + - if @poll.choices.empty? + = render partial: 'choice', locals: { value: '' } + = link_to t('.new_choice'), '#', 'data-add-choice' => true, class: 'btn' + .form-actions + = f.button :submit + = link_to t('ui.or_cancel'), :back diff --git a/plugins/polls/app/views/polls/_polls.html.haml b/plugins/polls/app/views/polls/_polls.html.haml new file mode 100644 index 00000000..3cb37cda --- /dev/null +++ b/plugins/polls/app/views/polls/_polls.html.haml @@ -0,0 +1,23 @@ +- if Poll.count > 20 + = items_per_page += pagination_links_remote @polls +%table.table.table-striped + %thead + %tr + %th= heading_helper Poll, :name + %th= heading_helper Poll, :starts + %th= heading_helper Poll, :ends + %th= t 'ui.actions' + %tbody + - for poll in @polls + %tr{:class => cycle('even','odd', :name => 'polls')} + %td= link_to poll.name, poll + %td= format_date poll.starts + %td= format_date poll.ends + %td + - if poll.user_can_vote?(current_user) + = link_to t('.vote'), vote_poll_path(poll), class: 'btn btn-mini btn-success' + - if poll.user_can_edit?(current_user) + = link_to t('ui.edit'), edit_poll_path(poll), class: 'btn btn-mini' + = link_to t('ui.delete'), poll, :data => {:confirm => t('ui.confirm_delete', name: poll.name)}, + :method => :delete, class: 'btn btn-mini btn-danger' diff --git a/plugins/polls/app/views/polls/edit.html.haml b/plugins/polls/app/views/polls/edit.html.haml new file mode 100644 index 00000000..7724f1b7 --- /dev/null +++ b/plugins/polls/app/views/polls/edit.html.haml @@ -0,0 +1,3 @@ +- title t '.title' + += render 'form' diff --git a/plugins/polls/app/views/polls/index.html.haml b/plugins/polls/app/views/polls/index.html.haml new file mode 100644 index 00000000..215dcc8e --- /dev/null +++ b/plugins/polls/app/views/polls/index.html.haml @@ -0,0 +1,7 @@ +- title t('.title') + +- content_for :actionbar do + = link_to t('.new_poll'), new_poll_path, class: 'btn btn-primary' + +#polls + = render 'polls' diff --git a/plugins/polls/app/views/polls/index.js.haml b/plugins/polls/app/views/polls/index.js.haml new file mode 100644 index 00000000..303dba7d --- /dev/null +++ b/plugins/polls/app/views/polls/index.js.haml @@ -0,0 +1 @@ +$('#polls').html('#{escape_javascript(render("polls"))}'); diff --git a/plugins/polls/app/views/polls/new.html.haml b/plugins/polls/app/views/polls/new.html.haml new file mode 100644 index 00000000..7724f1b7 --- /dev/null +++ b/plugins/polls/app/views/polls/new.html.haml @@ -0,0 +1,3 @@ +- title t '.title' + += render 'form' diff --git a/plugins/polls/app/views/polls/show.html.haml b/plugins/polls/app/views/polls/show.html.haml new file mode 100644 index 00000000..19e0aeff --- /dev/null +++ b/plugins/polls/app/views/polls/show.html.haml @@ -0,0 +1,69 @@ +- title @poll.name + +- content_for :actionbar do + - if @poll.user_can_vote?(current_user) + = link_to t('.vote'), vote_poll_path(@poll), class: 'btn btn-success' + - if @poll.user_can_edit?(current_user) + = link_to t('ui.edit'), edit_poll_path(@poll), class: 'btn' + = link_to t('ui.delete'), @poll, :data => {:confirm => t('.confirm')}, :method => :delete, class: 'btn btn-danger' + +%p= simple_format @poll.description + +- sums = [] +%table.table.table-striped + %thead + %tr + %th= heading_helper PollVote, :name + - for choice in @poll.choices + - sums << 0 + %th= choice + %th= heading_helper PollVote, :updated_at + %tbody + - for vote in @poll.poll_votes.includes(:poll_choices) + %tr + %td + - if vote.ordergroup.nil? + = show_user vote.user + - else + = "#{vote.ordergroup.name} (#{show_user vote.user})" + - @poll.choices.size.times do |idx| + - if choice = vote.poll_choices.find { |choice| choice.choice == idx } + - if @poll.event? + - if choice.value == 0 + %td{style:'background-color:#eed3d7'}= "\u2715" + - elsif choice.value == 1 + - sums[idx] += 1 + %td{style:'background-color:#d6e9c6'}= "\u2714" + - else + - sums[idx] += 0.5 + %td{style:'background-color:#fcf8e3'}= "?" + - elsif @poll.single_select? || @poll.multi_select? + - sums[idx] += 1 + %td= "\u2717" + - else + - sums[idx] += choice.value + %td= choice.value + - else + %td + %td= format_time vote.updated_at + %tfoot + %tr + %td + - for sum in sums + %td + - best_sum = @poll.resistance_points? ? sums.min : sums.max + - if sum == best_sum + %strong= sum + - else + = sum + %td + +- for vote in @poll.poll_votes + - unless vote.note.empty? + .comment + %strong + - if vote.ordergroup.nil? + = show_user vote.user + - else + = "#{vote.ordergroup.name} (#{show_user vote.user})" + = simple_format(vote.note) diff --git a/plugins/polls/app/views/polls/vote.html.haml b/plugins/polls/app/views/polls/vote.html.haml new file mode 100644 index 00000000..47c506fa --- /dev/null +++ b/plugins/polls/app/views/polls/vote.html.haml @@ -0,0 +1,49 @@ +- title @poll.name + +%p= simple_format @poll.description + += form_tag(vote_poll_path(@poll)) do + - if @poll.event? + %table.table.table-striped + %thead + %tr + %th + %th= "\u2714" + %th= "?" + %th= "\u2715" + %tbody + - @poll.choices.each_with_index do |choice, idx| + %tr + %td= @poll.choices[idx] + %td= radio_button_tag "choices[#{idx}]", '1', @poll_vote.poll_choices.where(choice: idx, value: '1').any? + %td= radio_button_tag "choices[#{idx}]", '2', @poll_vote.poll_choices.where(choice: idx, value: '2').any? + %td= radio_button_tag "choices[#{idx}]", '0', @poll_vote.poll_choices.where(choice: idx, value: '0').any? + - elsif @poll.single_select? + - @poll.choices.each_with_index do |choice, idx| + = radio_button_tag 'choice', idx, @poll_vote.poll_choices.where(choice: idx, value: 1).any?, style: 'float:left' + = label_tag "choices[#{idx}]", choice + - elsif @poll.multi_select? + - @poll.choices.each_with_index do |choice, idx| + = check_box_tag "choices[#{idx}]", '1', @poll_vote.poll_choices.where(choice: idx, value: 1).any?, style: 'float:left' + = label_tag "choices[#{idx}]", choice + - elsif @poll.points? || @poll.resistance_points? + %table.table.table-striped + %thead + %tr + %th + - @poll.available_points.each do |point| + %th= point + %tbody + - @poll.choices.each_with_index do |choice, idx| + %tr + %td= @poll.choices[idx] + - @poll.available_points.each do |point| + %th= radio_button_tag "choices[#{idx}]", point, @poll_vote.poll_choices.where(choice: idx, value: point).any? + + %p + %b= heading_helper(PollVote, :note) + ':' + %p + = text_area_tag 'note', @poll_vote.note + %p + = submit_tag t('.submit'), class: 'btn btn-success' + = link_to t('ui.back'), @poll, class: 'btn' diff --git a/plugins/polls/config/locales/de.yml b/plugins/polls/config/locales/de.yml new file mode 100644 index 00000000..72860e9c --- /dev/null +++ b/plugins/polls/config/locales/de.yml @@ -0,0 +1,67 @@ +de: + activerecord: + attributes: + poll: + choices: Wahlmöglichkeiten + created_at: Erstellt am + created_by: Erstellt von + description: Beschreibung + ends: Endet am + name: Name + max_points: Maximalpunkte + min_points: Minimalpunkte + multi_select_count: Maximale Anzahl + one_vote_per_ordergroup: Nur eine Stimme pro Bestellgruppe + starts: Läuft vom + voting_method: Umfragetyp + voting_methods: + event: Termin + single_select: Einzelauswahl + multi_select: Mehrfachauswahl + points: Punkte + resistance_points: Widerstandspunkte + poll_vote: + name: Name + note: Notiz + updated_at: Zuletzt aktualisierst + models: + poll: Umfrage + config: + hints: + use_polls: Einfache Umfragen aktivieren + keys: + use_polls: Umfragen aktivieren + navigation: + polls: Umfragen + polls: + choice: + remove: Löschen + create: + error: 'Umfrage konnte nicht erstellt werden: %{error}' + notice: Umfrage wurde erstellt + edit: + title: Umfrage bearbeiten + form: + already_voted: Die Wahlmöglichkeiten können icht mehr geändert werden, da bereits Stimmen abgegeben wurden. + new_choice: Zusätzliche Wahlmöglichkeit + required_ordergroup_custom_field: '''%{label}'' von Bestellgruppe darf nicht leer sein' + required_user_custom_field: '''%{label}'' von Benutzer_in darf nicht leer sein' + destroy: + error: 'Umfrage konnte nicht gelöscht werden: %{error}' + no_right: Du hast nicht genügend Rechte um die Umfrage zu löschen + notice: Umfrage wurde gelöscht + index: + new_poll: Neue Umfrage + title: Umfragen + new: + title: Umfrage erstellen + polls: + vote: Abstimmen + show: + vote: Abstimmen + update: + error: 'Umfrage konnte nicht aktualisiert werden: %{error}' + notice: Umfrage wurde aktualisiert + vote: + submit: Abstimmen + no_right: Du kannst bei dieser Umfrage nicht teilnehmen diff --git a/plugins/polls/config/locales/en.yml b/plugins/polls/config/locales/en.yml new file mode 100644 index 00000000..bc79481e --- /dev/null +++ b/plugins/polls/config/locales/en.yml @@ -0,0 +1,67 @@ +en: + activerecord: + attributes: + poll: + choices: Choices + created_at: Created at + created_by: Created by + description: Description + ends: Ends at + name: Name + max_points: Maximal points + min_points: Minimal points + multi_select_count: Maximal number + one_vote_per_ordergroup: Only one vote for every ordergroup + starts: Starts at + voting_method: Voting method + voting_methods: + event: Event + single_select: Single select + multi_select: Muliple selections + points: Points + resistance_points: Resistance points + poll_vote: + name: Name + note: Note + updated_at: Last updated + models: + poll: Poll + config: + hints: + use_polls: Enable simple polls. + keys: + use_polls: Enable polls + navigation: + polls: Polls + polls: + choice: + remove: Delete + create: + error: 'Poll could not be created: %{error}' + notice: Poll has been created + edit: + title: Edit poll + form: + already_voted: The choices can not be changed, since there have been votes already. + new_choice: Additional choice + required_ordergroup_custom_field: '''%{label}'' of ordergroup must not be empty' + required_user_custom_field: '''%{label}'' of use must not be empty' + destroy: + error: 'Poll could not be deleted: %{error}' + no_right: You do not have enough rights to delte this poll + notice: Poll has been deleted + index: + new_poll: New poll + title: Polls + new: + title: New poll + polls: + vote: Vote + show: + vote: Vote + update: + error: 'Poll could not be updated: %{error}' + notice: Poll has been updated + vote: + submit: Vote + no_right: You can not participate in this poll diff --git a/plugins/polls/config/routes.rb b/plugins/polls/config/routes.rb new file mode 100644 index 00000000..5375342c --- /dev/null +++ b/plugins/polls/config/routes.rb @@ -0,0 +1,14 @@ +Rails.application.routes.draw do + + scope '/:foodcoop' do + + resources :polls do + member do + get :vote + post :vote + end + end + + end + +end diff --git a/plugins/polls/db/migrate/20181110000000_create_polls.rb b/plugins/polls/db/migrate/20181110000000_create_polls.rb new file mode 100644 index 00000000..f45be067 --- /dev/null +++ b/plugins/polls/db/migrate/20181110000000_create_polls.rb @@ -0,0 +1,37 @@ +class CreatePolls < ActiveRecord::Migration + def change + create_table :polls do |t| + t.integer :created_by_user_id, null: false + t.string :name, null: false + t.text :description + t.datetime :starts + t.datetime :ends + t.boolean :one_vote_per_ordergroup, default: false, null: false + t.text :required_ordergroup_custom_fields + t.text :required_user_custom_fields + t.integer :voting_method, null: false + t.string :choices, array: true, null: false + t.integer :final_choice, index: true + t.integer :multi_select_count, default: 0, null: false + t.integer :min_points + t.integer :max_points + t.timestamps + end + + create_table :poll_votes do |t| + t.references :poll, null: false + t.references :user, null: false + t.references :ordergroup + t.text :note + t.timestamps + t.index [:poll_id, :user_id, :ordergroup_id], unique: true + end + + create_table :poll_choices do |t| + t.references :poll_vote, null: false + t.integer :choice, null: false + t.integer :value, null: false + t.index [:poll_vote_id, :choice], unique: true + end + end +end diff --git a/plugins/polls/foodsoft_polls.gemspec b/plugins/polls/foodsoft_polls.gemspec new file mode 100644 index 00000000..e5b25173 --- /dev/null +++ b/plugins/polls/foodsoft_polls.gemspec @@ -0,0 +1,21 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "foodsoft_polls/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "foodsoft_polls" + s.version = FoodsoftPolls::VERSION + s.authors = ["paroga"] + s.email = ["paroga@paroga.com"] + s.homepage = "https://github.com/foodcoops/foodsoft" + s.summary = "Polls plugin for foodsoft." + s.description = "Adds possibility to do polls with foodsoft." + + s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] + s.test_files = Dir["test/**/*"] + + s.add_dependency "rails" + s.add_dependency "deface", "~> 1.0" +end diff --git a/plugins/polls/lib/foodsoft_polls.rb b/plugins/polls/lib/foodsoft_polls.rb new file mode 100644 index 00000000..a25843c6 --- /dev/null +++ b/plugins/polls/lib/foodsoft_polls.rb @@ -0,0 +1,9 @@ +require 'foodsoft_polls/engine' + +module FoodsoftPolls + # Return whether the polls are used or not. + # Enabled by default in {FoodsoftConfig} since it used to be part of the foodsoft core. + def self.enabled? + FoodsoftConfig[:use_polls] + end +end diff --git a/plugins/polls/lib/foodsoft_polls/engine.rb b/plugins/polls/lib/foodsoft_polls/engine.rb new file mode 100644 index 00000000..b8bd1226 --- /dev/null +++ b/plugins/polls/lib/foodsoft_polls/engine.rb @@ -0,0 +1,15 @@ +module FoodsoftPolls + class Engine < ::Rails::Engine + def navigation(primary, context) + return unless FoodsoftPolls.enabled? + return if primary[:foodcoop].nil? + sub_nav = primary[:foodcoop].sub_navigation + sub_nav.items << + SimpleNavigation::Item.new(primary, :polls, I18n.t('navigation.polls'), context.polls_path) + # move to right before tasks item + if i = sub_nav.items.index(sub_nav[:tasks]) + sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) + end + end + end +end diff --git a/plugins/polls/lib/foodsoft_polls/version.rb b/plugins/polls/lib/foodsoft_polls/version.rb new file mode 100644 index 00000000..5f3fb96d --- /dev/null +++ b/plugins/polls/lib/foodsoft_polls/version.rb @@ -0,0 +1,3 @@ +module FoodsoftPolls + VERSION = "0.0.1" +end