diff --git a/.gitignore b/.gitignore index 35f5651a4..a6f2826c3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,5 @@ .DS_Store .ruby-gemset -public/sitemap.xml \ No newline at end of file +public/sitemap.xml +public/system/ diff --git a/Capfile b/Capfile index 1a33b63f0..ea11eb9b6 100644 --- a/Capfile +++ b/Capfile @@ -12,6 +12,10 @@ require 'capistrano/delayed_job' require 'whenever/capistrano' require 'rvm1/capistrano3' +#SCM: Git +require "capistrano/scm/git" +install_plugin Capistrano::SCM::Git + # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r } diff --git a/Gemfile b/Gemfile index 13a0b3567..39dee65f8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,20 +1,20 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.2.7.1' +gem 'rails', '4.2.8' # Use PostgreSQL -gem 'pg', '~> 0.19.0' +gem 'pg', '~> 0.20.0' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0', '>= 5.0.4' # Use Uglifier as compressor for JavaScript assets -gem 'uglifier', '>= 3.0.4' +gem 'uglifier', '~> 3.1.9' # Use CoffeeScript for .coffee assets and views gem 'coffee-rails', '~> 4.2.1' # See https://github.com/rails/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library -gem 'jquery-rails', '~> 4.2.2' +gem 'jquery-rails', '~> 4.3.1' gem 'jquery-ui-rails' # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks gem 'turbolinks' @@ -28,10 +28,10 @@ gem 'devise_security_extension' # gem 'bcrypt', '~> 3.1.7' gem 'omniauth' gem 'omniauth-twitter' -gem 'omniauth-facebook', '~> 3.0.0' +gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-google-oauth2', '~> 0.4.0' -gem 'kaminari' +gem 'kaminari', '~> 1.0.1' gem 'ancestry', '~> 2.2.2' gem 'acts-as-taggable-on' gem 'responders', '~> 2.3.0' @@ -40,30 +40,32 @@ gem 'foundation_rails_helper', '~> 2.0.0' gem 'acts_as_votable' gem 'ckeditor', '~> 4.2.2' gem 'invisible_captcha', '~> 0.9.2' -gem 'cancancan' -gem 'social-share-button' +gem 'cancancan', '~> 1.16.0' +gem 'social-share-button', '~> 0.10' gem 'initialjs-rails', '0.2.0.4' gem 'unicorn', '~> 5.2.0' -gem 'paranoia', '~> 2.2.0' +gem 'paranoia', '~> 2.2.1' gem 'rinku', '~> 2.0.2', require: 'rails_rinku' gem 'savon' gem 'dalli' -gem 'rollbar', '~> 2.14.0' +gem 'rollbar', '~> 2.14.1' gem 'delayed_job_active_record', '~> 4.1.0' gem 'daemons' gem 'devise-async' -gem 'newrelic_rpm', '~> 3.17.2.327' +gem 'newrelic_rpm', '~> 4.0.0.332' gem 'whenever', require: false gem 'pg_search' -gem 'sitemap_generator' +gem 'sitemap_generator', '~> 5.3.1' -gem 'ahoy_matey', '~> 1.5.3' -gem 'groupdate', '~> 3.1.0' # group temporary data +gem 'ahoy_matey', '~> 1.5.5' +gem 'groupdate', '~> 3.2.0' # group temporary data gem 'tolk', '~> 2.0.0' # Web interface for translations gem 'browser' gem 'turnout', '~> 2.4.0' -gem 'redcarpet' +gem 'redcarpet', '~> 3.4.0' + +gem 'paperclip' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console @@ -72,28 +74,28 @@ group :development, :test do gem 'spring' gem 'spring-commands-rspec' gem 'rspec-rails', '~> 3.5' - gem 'capybara' - gem 'factory_girl_rails' + gem 'capybara', '~> 2.13.0' + gem 'factory_girl_rails', '~> 4.8.0' gem 'fuubar' gem 'launchy' gem 'quiet_assets' - gem 'letter_opener_web', '~> 1.3.0' - gem 'i18n-tasks' - gem 'capistrano', '3.5.0', require: false + gem 'letter_opener_web', '~> 1.3.1' + gem 'i18n-tasks', '~> 0.9.12' + gem 'capistrano', '~> 3.8.0', require: false gem 'capistrano-bundler', '~> 1.2', require: false - gem "capistrano-rails", '1.1.8', require: false + gem "capistrano-rails", '~> 1.2.3', require: false gem 'rvm1-capistrano3', require: false - gem 'capistrano3-delayed-job', '~> 1.0' - gem "bullet" - gem "faker" - gem 'rubocop', '~> 0.45.0', require: false + gem 'capistrano3-delayed-job', '~> 1.7.3' + gem "bullet", '~> 5.5.1' + gem "faker", '~> 1.7.3' + gem 'rubocop', '~> 0.47.1', require: false gem 'knapsack' end group :test do gem 'database_cleaner' - gem 'poltergeist' - gem 'coveralls', require: false + gem 'poltergeist', '~> 1.14.0' + gem 'coveralls', '~> 0.8.19', require: false gem 'email_spec' end diff --git a/Gemfile.lock b/Gemfile.lock index e2c21605c..67ab5a78b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,46 +1,46 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) + actionmailer (4.2.8) + actionpack (= 4.2.8) + actionview (= 4.2.8) + activejob (= 4.2.8) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.7.1) - actionview (= 4.2.7.1) - activesupport (= 4.2.7.1) + actionpack (4.2.8) + actionview (= 4.2.8) + activesupport (= 4.2.8) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.7.1) - activesupport (= 4.2.7.1) + actionview (4.2.8) + activesupport (= 4.2.8) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.7.1) - activesupport (= 4.2.7.1) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (4.2.8) + activesupport (= 4.2.8) globalid (>= 0.3.0) - activemodel (4.2.7.1) - activesupport (= 4.2.7.1) + activemodel (4.2.8) + activesupport (= 4.2.8) builder (~> 3.1) - activerecord (4.2.7.1) - activemodel (= 4.2.7.1) - activesupport (= 4.2.7.1) + activerecord (4.2.8) + activemodel (= 4.2.8) + activesupport (= 4.2.8) arel (~> 6.0) - activesupport (4.2.7.1) + activesupport (4.2.8) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - acts-as-taggable-on (3.5.0) - activerecord (>= 3.2, < 5) + acts-as-taggable-on (4.0.0) + activerecord (>= 4.0) acts_as_votable (0.10.0) - addressable (2.4.0) - ahoy_matey (1.5.3) + addressable (2.5.0) + public_suffix (~> 2.0, >= 2.0.2) + ahoy_matey (1.5.5) addressable browser (~> 2.0) geocoder @@ -51,14 +51,14 @@ GEM safely_block (>= 0.1.1) user_agent_parser uuidtools - airbrussh (1.1.1) + airbrussh (1.1.2) sshkit (>= 1.6.1, != 1.7.0) akami (1.3.1) gyoku (>= 0.4.0) nokogiri ancestry (2.2.2) activerecord (>= 3.0.0) - arel (6.0.3) + arel (6.0.4) ast (2.3.0) babel-source (5.8.35) babel-transpiler (0.7.0) @@ -66,28 +66,27 @@ GEM execjs (~> 2.0) bcrypt (3.1.11) browser (2.3.0) - builder (3.2.2) - bullet (5.4.2) + builder (3.2.3) + bullet (5.5.1) activesupport (>= 3.0.0) uniform_notifier (~> 1.10.0) byebug (9.0.6) - cancancan (1.15.0) - capistrano (3.5.0) + cancancan (1.16.0) + capistrano (3.8.0) airbrussh (>= 1.0.0) - capistrano-harrow i18n rake (>= 10.0.0) sshkit (>= 1.9.0) capistrano-bundler (1.2.0) capistrano (~> 3.1) sshkit (~> 1.2) - capistrano-harrow (0.5.3) - capistrano-rails (1.1.8) + capistrano-rails (1.2.3) capistrano (~> 3.1) capistrano-bundler (~> 1.1) - capistrano3-delayed-job (1.7.2) + capistrano3-delayed-job (1.7.3) capistrano (~> 3.0, >= 3.0.0) - capybara (2.7.1) + daemons (~> 1.2.4) + capybara (2.13.0) addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -109,9 +108,9 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) - concurrent-ruby (1.0.4) - coveralls (0.8.17) + coffee-script-source (1.12.2) + concurrent-ruby (1.0.5) + coveralls (0.8.19) json (>= 1.8, < 3) simplecov (~> 0.12.0) term-ansicolor (~> 1.3) @@ -151,14 +150,14 @@ GEM errbase (0.0.3) erubis (2.7.0) execjs (2.7.0) - factory_girl (4.7.0) + factory_girl (4.8.0) activesupport (>= 3.0.0) - factory_girl_rails (4.7.0) - factory_girl (~> 4.7.0) + factory_girl_rails (4.8.0) + factory_girl (~> 4.8.0) railties (>= 3.0.0) - faker (1.6.6) + faker (1.7.3) i18n (~> 0.5) - faraday (0.9.2) + faraday (0.11.0) multipart-post (>= 1.2, < 3) foundation-rails (6.2.4.0) railties (>= 3.1.0) @@ -173,20 +172,20 @@ GEM fuubar (2.2.0) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - geocoder (1.4.1) + geocoder (1.4.3) globalid (0.3.7) activesupport (>= 4.1.0) - groupdate (3.1.1) + groupdate (3.2.0) activesupport (>= 3) gyoku (1.3.1) builder (>= 2.1.2) - hashie (3.4.6) + hashie (3.5.5) highline (1.7.8) htmlentities (4.3.4) httpi (2.4.1) rack - i18n (0.7.0) - i18n-tasks (0.9.6) + i18n (0.8.1) + i18n-tasks (0.9.12) activesupport (>= 4.0.2) ast (>= 2.1.0) easy_translate (>= 0.5.0) @@ -200,26 +199,35 @@ GEM railties (>= 3.1, < 6.0) invisible_captcha (0.9.2) rails (>= 3.2.0) - jquery-rails (4.2.2) + jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - jquery-ui-rails (5.0.5) + jquery-ui-rails (6.0.1) railties (>= 3.2.16) - json (1.8.3) - jwt (1.5.4) - kaminari (0.17.0) - actionpack (>= 3.0.0) - activesupport (>= 3.0.0) + json (2.0.3) + jwt (1.5.6) + kaminari (1.0.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.0.1) + kaminari-activerecord (= 1.0.1) + kaminari-core (= 1.0.1) + kaminari-actionview (1.0.1) + actionview + kaminari-core (= 1.0.1) + kaminari-activerecord (1.0.1) + activerecord + kaminari-core (= 1.0.1) + kaminari-core (1.0.1) kgio (2.11.0) - knapsack (1.13.1) + knapsack (1.13.2) rake timecop (>= 0.1.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) launchy (~> 2.2) - letter_opener_web (1.3.0) + letter_opener_web (1.3.1) actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) @@ -230,29 +238,30 @@ GEM mime-types (3.1) mime-types-data (~> 3.2015) mime-types-data (3.2016.0521) + mimemagic (0.3.2) mini_portile2 (2.1.0) minitest (5.10.1) multi_json (1.12.1) - multi_xml (0.5.5) + multi_xml (0.6.0) multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (3.2.0) - newrelic_rpm (3.17.2.327) - nokogiri (1.6.8.1) + net-ssh (4.1.0) + newrelic_rpm (4.0.0.332) + nokogiri (1.7.1) mini_portile2 (~> 2.1.0) nori (2.6.0) - oauth (0.5.0) - oauth2 (1.0.0) - faraday (>= 0.8, < 0.10) + oauth (0.5.1) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (~> 1.2) - omniauth (1.3.1) - hashie (>= 1.2, < 4) - rack (>= 1.0, < 3) - omniauth-facebook (3.0.0) + rack (>= 1.2, < 3) + omniauth (1.6.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-facebook (4.0.0) omniauth-oauth2 (~> 1.2) omniauth-google-oauth2 (0.4.1) jwt (~> 1.5.2) @@ -265,24 +274,31 @@ GEM omniauth-oauth2 (1.4.0) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-twitter (1.2.1) - json (~> 1.3) + omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) + rack orm_adapter (0.5.0) - paranoia (2.2.0) + paperclip (5.1.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + cocaine (~> 0.5.5) + mime-types + mimemagic (~> 0.3.0) + paranoia (2.2.1) activerecord (>= 4.0, < 5.1) - parser (2.3.3.1) + parser (2.4.0.0) ast (~> 2.2) - pg (0.19.0) - pg_search (1.0.6) - activerecord (>= 3.1) - activesupport (>= 3.1) - arel - poltergeist (1.10.0) + pg (0.20.0) + pg_search (2.0.1) + activerecord (>= 4.2) + activesupport (>= 4.2) + arel (>= 6) + poltergeist (1.14.0) capybara (~> 2.1) cliver (~> 0.3.1) websocket-driver (>= 0.2.0) powerpack (0.1.1) + public_suffix (2.0.5) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) rack (1.6.5) @@ -292,40 +308,40 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.7.1) - actionmailer (= 4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - activemodel (= 4.2.7.1) - activerecord (= 4.2.7.1) - activesupport (= 4.2.7.1) + rails (4.2.8) + actionmailer (= 4.2.8) + actionpack (= 4.2.8) + actionview (= 4.2.8) + activejob (= 4.2.8) + activemodel (= 4.2.8) + activerecord (= 4.2.8) + activesupport (= 4.2.8) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.7.1) + railties (= 4.2.8) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) + rails-dom-testing (1.0.8) activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.7.1) - actionpack (= 4.2.7.1) - activesupport (= 4.2.7.1) + railties (4.2.8) + actionpack (= 4.2.8) + activesupport (= 4.2.8) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.1.0) + rainbow (2.2.1) raindrops (0.17.0) rake (12.0.0) - redcarpet (3.3.4) + redcarpet (3.4.0) referer-parser (0.3.0) - request_store (1.3.1) + request_store (1.3.2) responders (2.3.0) railties (>= 4.2.0, < 5.1) rinku (2.0.2) - rollbar (2.14.0) + rollbar (2.14.1) multi_json rspec-core (3.5.4) rspec-support (~> 3.5.0) @@ -344,8 +360,8 @@ GEM rspec-mocks (~> 3.5.0) rspec-support (~> 3.5.0) rspec-support (3.5.0) - rubocop (0.45.0) - parser (>= 2.3.1.1, < 3.0) + rubocop (0.47.1) + parser (>= 2.3.3.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.7) @@ -355,9 +371,9 @@ GEM capistrano (~> 3.0) sshkit (>= 1.2) safe_yaml (1.0.4) - safely_block (0.1.1) + safely_block (0.2.0) errbase - sass (3.4.22) + sass (3.4.23) sass-rails (5.0.6) railties (>= 4.0.0, < 6) sass (~> 3.1) @@ -377,12 +393,12 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - sitemap_generator (5.2.0) + sitemap_generator (5.3.1) builder (~> 3.0) - social-share-button (0.3.1) + social-share-button (0.10.0) coffee-rails - sass-rails - spring (1.7.2) + spring (2.0.1) + activesupport (>= 4.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) sprockets (3.7.1) @@ -396,7 +412,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.11.4) + sshkit (1.12.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) term-ansicolor (1.4.0) @@ -405,10 +421,10 @@ GEM unicode-display_width (~> 1.1.1) thor (0.19.4) thread (0.2.2) - thread_safe (0.3.5) - tilt (2.0.5) + thread_safe (0.3.6) + tilt (2.0.7) timecop (0.8.1) - tins (1.13.0) + tins (1.13.2) tolk (2.0.0) rails (>= 4.0) safe_yaml (>= 0.8.6) @@ -421,9 +437,9 @@ GEM tilt (>= 1.4, < 3) tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (3.0.4) + uglifier (3.1.9) execjs (>= 0.3.0, < 3) - unicode-display_width (1.1.1) + unicode-display_width (1.1.3) unicorn (5.2.0) kgio (~> 2.6) raindrops (~> 0.7) @@ -439,7 +455,7 @@ GEM activemodel (>= 4.2) debug_inspector railties (>= 4.2) - websocket-driver (0.6.4) + websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) whenever (0.9.7) @@ -453,20 +469,20 @@ PLATFORMS DEPENDENCIES acts-as-taggable-on acts_as_votable - ahoy_matey (~> 1.5.3) + ahoy_matey (~> 1.5.5) ancestry (~> 2.2.2) browser - bullet + bullet (~> 5.5.1) byebug - cancancan - capistrano (= 3.5.0) + cancancan (~> 1.16.0) + capistrano (~> 3.8.0) capistrano-bundler (~> 1.2) - capistrano-rails (= 1.1.8) - capistrano3-delayed-job (~> 1.0) - capybara + capistrano-rails (~> 1.2.3) + capistrano3-delayed-job (~> 1.7.3) + capybara (~> 2.13.0) ckeditor (~> 4.2.2) coffee-rails (~> 4.2.1) - coveralls + coveralls (~> 0.8.19) daemons dalli database_cleaner @@ -475,50 +491,54 @@ DEPENDENCIES devise-async devise_security_extension email_spec - factory_girl_rails - faker + factory_girl_rails (~> 4.8.0) + faker (~> 1.7.3) foundation-rails (~> 6.2.4.0) foundation_rails_helper (~> 2.0.0) fuubar - groupdate (~> 3.1.0) - i18n-tasks + groupdate (~> 3.2.0) + i18n-tasks (~> 0.9.12) initialjs-rails (= 0.2.0.4) invisible_captcha (~> 0.9.2) - jquery-rails (~> 4.2.2) + jquery-rails (~> 4.3.1) jquery-ui-rails - kaminari + kaminari (~> 1.0.1) knapsack launchy - letter_opener_web (~> 1.3.0) - newrelic_rpm (~> 3.17.2.327) + letter_opener_web (~> 1.3.1) + newrelic_rpm (~> 4.0.0.332) omniauth - omniauth-facebook (~> 3.0.0) + omniauth-facebook (~> 4.0.0) omniauth-google-oauth2 (~> 0.4.0) omniauth-twitter - paranoia (~> 2.2.0) - pg (~> 0.19.0) + paperclip + paranoia (~> 2.2.1) + pg (~> 0.20.0) pg_search - poltergeist + poltergeist (~> 1.14.0) quiet_assets - rails (= 4.2.7.1) - redcarpet + rails (= 4.2.8) + redcarpet (~> 3.4.0) responders (~> 2.3.0) rinku (~> 2.0.2) - rollbar (~> 2.14.0) + rollbar (~> 2.14.1) rspec-rails (~> 3.5) - rubocop (~> 0.45.0) + rubocop (~> 0.47.1) rvm1-capistrano3 sass-rails (~> 5.0, >= 5.0.4) savon - sitemap_generator - social-share-button + sitemap_generator (~> 5.3.1) + social-share-button (~> 0.10) spring spring-commands-rspec sprockets (~> 3.7.1) tolk (~> 2.0.0) turbolinks turnout (~> 2.4.0) - uglifier (>= 3.0.4) + uglifier (~> 3.1.9) unicorn (~> 5.2.0) web-console (= 3.3.0) whenever + +BUNDLED WITH + 1.14.6 diff --git a/README.md b/README.md index c62fcfeec..20ed21003 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ -![Logo of Consul] -(https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) +![Logo of Consul](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) # Consul Citizen Participation and Open Government Application -[![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/consul/consul.svg?branch=master)](https://travis-ci.org/consul/consul) [![Code Climate](https://codeclimate.com/github/consul/consul/badges/gpa.svg)](https://codeclimate.com/github/consul/consul) [![Dependency Status](https://gemnasium.com/consul/consul.svg)](https://gemnasium.com/consul/consul) [![Coverage Status](https://coveralls.io/repos/github/consul/consul/badge.svg?branch=master)](https://coveralls.io/github/consul/consul?branch=master) +[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](http://www.gnu.org/licenses/agpl-3.0) + +[![Accessibility conformance](https://img.shields.io/badge/accessibility-WAI:AA-green.svg)](https://www.w3.org/WAI/eval/Overview) +[![A11y issues checked with Rocket Validator](https://rocketvalidator.com/badges/checked_with_rocket_validator.svg?url=https://rocketvalidator.com)](https://rocketvalidator.com/opensource) + +[![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) This is the opensource code repository of the eParticipation website originally developed for the Madrid City government eParticipation website diff --git a/README_ES.md b/README_ES.md index 1b197e983..2c1e362c3 100644 --- a/README_ES.md +++ b/README_ES.md @@ -1,15 +1,20 @@ -![Logotipo de Consul] -(https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) +![Logotipo de Consul](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) # Consul Aplicación de Participación Ciudadana y Gobierno Abierto -[![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/consul/consul.svg?branch=master)](https://travis-ci.org/consul/consul) [![Code Climate](https://codeclimate.com/github/consul/consul/badges/gpa.svg)](https://codeclimate.com/github/consul/consul) [![Dependency Status](https://gemnasium.com/consul/consul.svg)](https://gemnasium.com/consul/consul) [![Coverage Status](https://coveralls.io/repos/github/consul/consul/badge.svg?branch=master)](https://coveralls.io/github/consul/consul?branch=master) +[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](http://www.gnu.org/licenses/agpl-3.0) + +[![Accessibility conformance](https://img.shields.io/badge/accessibility-WAI:AA-green.svg)](https://www.w3.org/WAI/eval/Overview) +[![A11y issues checked with Rocket Validator](https://rocketvalidator.com/badges/checked_with_rocket_validator.svg?url=https://rocketvalidator.com)](https://rocketvalidator.com/opensource) + +[![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + Este es el repositorio de código abierto de la Aplicación de Participación Ciudadana Consul, creada originariamente por el Ayuntamiento de Madrid. diff --git a/app/assets/fonts/icons.eot b/app/assets/fonts/icons.eot index c3c5d6289..60cfbaddb 100644 Binary files a/app/assets/fonts/icons.eot and b/app/assets/fonts/icons.eot differ diff --git a/app/assets/fonts/icons.svg b/app/assets/fonts/icons.svg index a548d9575..f45adea5a 100644 --- a/app/assets/fonts/icons.svg +++ b/app/assets/fonts/icons.svg @@ -56,6 +56,9 @@ - + + + + diff --git a/app/assets/fonts/icons.ttf b/app/assets/fonts/icons.ttf index 6f938f863..8f791759e 100644 Binary files a/app/assets/fonts/icons.ttf and b/app/assets/fonts/icons.ttf differ diff --git a/app/assets/fonts/icons.woff b/app/assets/fonts/icons.woff index f3c31e804..0eef00837 100644 Binary files a/app/assets/fonts/icons.woff and b/app/assets/fonts/icons.woff differ diff --git a/app/assets/images/more_info/budgets_en.png b/app/assets/images/more_info/budgets_en.png new file mode 100644 index 000000000..1c1002145 Binary files /dev/null and b/app/assets/images/more_info/budgets_en.png differ diff --git a/app/assets/images/more_info/budgets_es.png b/app/assets/images/more_info/budgets_es.png new file mode 100644 index 000000000..89d93947b Binary files /dev/null and b/app/assets/images/more_info/budgets_es.png differ diff --git a/app/assets/images/more_info/budgets_fr.png b/app/assets/images/more_info/budgets_fr.png new file mode 100644 index 000000000..1c1002145 Binary files /dev/null and b/app/assets/images/more_info/budgets_fr.png differ diff --git a/app/assets/images/more_info/budgets_pt-BR.png b/app/assets/images/more_info/budgets_pt-BR.png new file mode 100644 index 000000000..1c1002145 Binary files /dev/null and b/app/assets/images/more_info/budgets_pt-BR.png differ diff --git a/app/assets/images/more_info/debates.png b/app/assets/images/more_info/debates.png new file mode 100644 index 000000000..30b195668 Binary files /dev/null and b/app/assets/images/more_info/debates.png differ diff --git a/app/assets/images/more_info/proposals_en.png b/app/assets/images/more_info/proposals_en.png new file mode 100644 index 000000000..60357f222 Binary files /dev/null and b/app/assets/images/more_info/proposals_en.png differ diff --git a/app/assets/images/more_info/proposals_es.png b/app/assets/images/more_info/proposals_es.png new file mode 100644 index 000000000..3466949bb Binary files /dev/null and b/app/assets/images/more_info/proposals_es.png differ diff --git a/app/assets/images/more_info/proposals_fr.png b/app/assets/images/more_info/proposals_fr.png new file mode 100644 index 000000000..60357f222 Binary files /dev/null and b/app/assets/images/more_info/proposals_fr.png differ diff --git a/app/assets/images/more_info/proposals_pt-BR.png b/app/assets/images/more_info/proposals_pt-BR.png new file mode 100644 index 000000000..60357f222 Binary files /dev/null and b/app/assets/images/more_info/proposals_pt-BR.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index fba539eeb..928f06edd 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,8 +12,8 @@ // //= require jquery //= require jquery_ujs -//= require jquery-ui/datepicker -//= require jquery-ui/datepicker-es +//= require jquery-ui/widgets/datepicker +//= require jquery-ui/i18n/datepicker-es //= require foundation //= require turbolinks //= require ckeditor/loader @@ -49,6 +49,7 @@ //= require fixed_bar //= require banners //= require social_share +//= require checkbox_toggle //= require custom var initialize_modules = function() { @@ -74,6 +75,7 @@ var initialize_modules = function() { App.FixedBar.initialize(); App.Banners.initialize(); App.SocialShare.initialize(); + App.CheckboxToggle.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/checkbox_toggle.js.coffee b/app/assets/javascripts/checkbox_toggle.js.coffee new file mode 100644 index 000000000..096ce7e25 --- /dev/null +++ b/app/assets/javascripts/checkbox_toggle.js.coffee @@ -0,0 +1,12 @@ +App.CheckboxToggle = + + initialize: -> + $('[data-checkbox-toggle]').on 'change', -> + $this = $(this) + $target = $($this.data('checkbox-toggle')) + if $this.is(':checked') + $target.show() + else + $target.hide() + + diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index cc5bf015e..b804d62ba 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -80,6 +80,7 @@ $budget: #7E328A; $budget-hover: #7571BF; $highlight: #E7F2FC; +$light: #F5F7FA; $featured: #FFDC5C; $footer-border: #BFC1C3; diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 90dee613a..fac6d35ed 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -5,6 +5,7 @@ // 03. List elements // 04. Stats // 05. Management +// 06. Polls // // 01. Global styles @@ -20,6 +21,11 @@ body.admin { .top-links { background: darken($admin-color, 15%); } + + .back-web { + padding-top: $line-height/4; + text-decoration: underline; + } } .top-bar { @@ -27,19 +33,37 @@ body.admin { height: auto; } + .top-bar-title { + + h1 { + margin-bottom: 0; + } + } + form { .button { margin-top: 0; + + &.margin-top { + margin-top: $line-height; + } } input[type="text"], textarea { width: 100%; } - .input-group input[type="text"] { - border-radius: 0; - margin-bottom: 0 !important; + .fieldset { + + select { + height: $line-height*2; + } + + .input-group input[type="text"] { + border-radius: 0; + margin-bottom: 0 !important; + } } } @@ -48,6 +72,15 @@ body.admin { th { text-align: left; + &.text-center { + text-align: center; + } + + &.text-right { + padding-right: $line-height; + text-align: right; + } + &.with-button { line-height: $line-height*2; } @@ -62,9 +95,20 @@ body.admin { } } + &.fixed { + table-layout: fixed; + } + input[type="submit"] ~ a, a ~ a { - margin-left: $line-height/2; - margin-right: $line-height/2; + margin-left: 0; + margin-right: 0; + margin-top: $line-height/2; + + @include breakpoint(medium) { + margin-left: $line-height/2; + margin-right: $line-height/2; + margin-top: 0; + } } } @@ -77,6 +121,11 @@ body.admin { color: $admin-color; } + .tabs-panel { + padding-left: 0; + padding-right: 0; + } + #proposals { width: 100% !important; } @@ -148,6 +197,13 @@ body.admin { } } +.input-group { + + .input-group-button { + padding-bottom: rem-calc(16); + } +} + // 02. Sidebar // ----------- @@ -155,7 +211,7 @@ body.admin { border-right: 1px solid $border; @include breakpoint(medium) { - padding-bottom: $line-height*3; + min-height: rem-calc(1100); } ul { @@ -165,10 +221,12 @@ body.admin { padding: 0; [class^="icon-"] { + color: $admin-color; display: inline-block; font-size: rem-calc(24); - padding-right: rem-calc(12); - padding-top: rem-calc(4); + line-height: $line-height; + padding: $line-height/2 $line-height/4; + padding-left: 0; vertical-align: middle; } @@ -177,19 +235,26 @@ body.admin { margin: 0; outline: 0; + ul { + margin-left: $line-height/1.5; + border-left: 1px solid $border; + padding-left: $line-height/2; + } + + &.section-title { + border-bottom: 1px solid $border; + } + &.active a { background: #f3f6f7; + border-radius: rem-calc(6); + -moz-border-radius: rem-calc(6); + -webkit-border-radius: rem-calc(6); color: $admin-color; font-weight: bold; } } - li.section { - border-bottom: 1px dotted #d5d5d5; - border-top: 1px dotted #d5d5d5; - height: $line-height/2; - } - li a { color: $text; display: block; @@ -199,10 +264,38 @@ body.admin { &:hover { background: #f3f6f7; + border-radius: rem-calc(6); + -moz-border-radius: rem-calc(6); + -webkit-border-radius: rem-calc(6); + color: $admin-color; text-decoration: none; } } } + + .is-accordion-submenu-parent { + + & > a::after { + border-color: $admin-color transparent transparent; + } + } + + .submenu { + border-bottom: 0; + margin-left: $line-height; + + li:first-child { + padding-top: $line-height/2; + } + + li:last-child { + padding-bottom: $line-height/2; + } + + a { + font-weight: normal; + } + } } // 03. List elements @@ -403,3 +496,46 @@ table.investment-projects-summary { white-space: nowrap; } } + +.geozone { + background: #ececec; + border-radius: rem-calc(6); + color: $text; + display: inline-block; + font-size: $small-font-size; + margin-bottom: $line-height/3; + padding: $line-height/4 $line-height/3; + text-decoration: none; + + &:hover { + background: #e0e0e0; + } +} + +// 06. Polls +// ----------------- + +.count-error { + background: $alert-bg !important; + color: $color-alert; + font-weight: bold; +} + +table { + + .callout { + height: $line-height*2; + line-height: $line-height*2; + padding: 0 $line-height/2; + } +} + +// 07. CMS +// -------------- +.cms_page_list { + + [class^="icon-"] { + padding-right: $menu-icon-spacing; + vertical-align: middle; + } +} diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 36e055a4e..0720c103f 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -6,6 +6,7 @@ @import "admin"; @import "layout"; @import "participation"; +@import "pages"; @import "custom"; @import "c3"; @import "annotator.min"; diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index f3d8c003e..afbcc8bdd 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -163,6 +163,12 @@ .icon-whatsapp:before { content: "\50"; } +.icon-zip:before { + content: "\4f"; +} +.icon-banner:before { + content: "\51"; +} .icon-arrow-down:before { content: "\52"; } @@ -184,3 +190,6 @@ .icon-checkmark-circle:before { content: "\59"; } +.icon-telegram:before { + content: "\31"; +} diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 1511910b1..94d403528 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -144,6 +144,10 @@ a { padding-top: $line-height; } +.light { + background: $light; +} + .highlight { background: $highlight; } @@ -204,6 +208,30 @@ a { } } +.menu.vertical { + background: white; + margin: $line-height 0; + padding: $line-height; + + li { + margin-bottom: $line-height; + + a { + color: $text-medium; + padding: 0; + } + + h2 { + font-size: $base-font-size; + } + + &.active { + border-bottom: 2px solid $brand; + color: $brand; + } + } +} + .small { font-size: $small-font-size; } @@ -462,6 +490,7 @@ header { .input-group-button { line-height: $line-height*1.5; + padding-bottom: 0; button { background: $border; @@ -483,7 +512,6 @@ header { } .submenu { - background: white; border-bottom: 1px solid $border; clear: both; margin-bottom: $line-height/2; @@ -585,7 +613,8 @@ footer { // 04. Tags // -------- -.tags a , .tag-cloud a, .categories a, .geozone a, .sidebar-links a { +.tags a , .tag-cloud a, .categories a, .geozone a, .sidebar-links a, +.tags span { background: #ececec; border-radius: rem-calc(6); color: $text; @@ -1104,11 +1133,12 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { // -------------------- [class^="level-"] { - color: white; + color: black; } .is-author { - background: #008CCF; + background: #00A5F1; + color: black; } .is-association { @@ -1166,7 +1196,11 @@ table { .button.button-twitter, .button.button-facebook, -.button.button-google { +.button.button-google, +.button.button-telegram { + background: white; + color: $text; + font-weight: bold; height: $line-height*2; line-height: $line-height*2; padding: 0; @@ -1174,9 +1208,11 @@ table { } .button.button-twitter { - background: #45B0E3; + background: #ECF7FC; + border-left: 3px solid #45B0E3; &:before { + color: #45B0E3; content: "f"; font-family: "icons" !important; font-size: rem-calc(24); @@ -1214,9 +1250,11 @@ table { } .button.button-facebook { - background: #3B5998; + background: #EBEEF4; + border-left: 3px solid #3B5998; &:before { + color: #3B5998; content: "A"; font-family: "icons" !important; font-size: rem-calc(24); @@ -1254,9 +1292,11 @@ table { } .button.button-google { - background: #DE4C34; + background: #FCEDEA; + border-left: 3px solid #DE4C34; &:before { + color: #DE4C34; content: "B"; font-family: "icons" !important; font-size: rem-calc(24); @@ -1293,6 +1333,48 @@ table { } } +.button.button-telegram { + background: #ECF7FC; + border-left: 3px solid #0088cc; + + &:before { + color: #0088cc; + content: "1"; + font-family: "icons" !important; + font-size: rem-calc(24); + left: 0; + line-height: $line-height*2; + padding: 0 rem-calc(20); + position: absolute; + top: 0; + } +} + +.ssb-telegram { + background: #0088cc; + background-image: none !important; + color: white; + height: $line-height*2 !important; + position: relative; + width: $line-height*2 !important; + + &:before { + content: "1"; + font-family: "icons" !important; + font-size: rem-calc(24); + left: 50%; + line-height: $line-height*2; + margin-left: rem-calc(-11); + position: absolute; + top: 0; + } + + &:hover, &:focus { + background: white; + color: #40A2D1; + } +} + .social { a { @@ -1384,6 +1466,30 @@ table { color: #CE3E26; } } + + .ssb-telegram { + background: #0088cc; + color: white; + height: $line-height; + position: relative; + width: $line-height*2; + + &:before { + content: "A"; + font-family: "icons" !important; + font-size: rem-calc(24); + left: 50%; + line-height: $line-height*2; + margin-left: rem-calc(-11); + position: absolute; + top: 0; + } + + &:hover, &:focus { + background: white; + color: #40A2D1; + } + } } // 13. Pages diff --git a/app/assets/stylesheets/pages.scss b/app/assets/stylesheets/pages.scss new file mode 100644 index 000000000..f8bf6c7cd --- /dev/null +++ b/app/assets/stylesheets/pages.scss @@ -0,0 +1,111 @@ +// Table of Contents +// +// 01. Header +// 02. Navigation +// 03. Content +// 04. Sidebar +// + +// 01. Header +// ---------------------- + +.jumbo { + margin-bottom: $line-height; + margin-top: rem-calc(-24); + padding-bottom: $line-height; + padding-top: $line-height; + + &.light { + background: #ECF0F1; + } +} + +.lead { + font-size: rem-calc(24); +} + +// 03. Navigation +// ---------------------- + +.menu-pages { + list-style-type: none; + margin: 0; + + li { + display: block; + + @include breakpoint(medium) { + display: inline-block; + margin-right: $line-height/2; + } + } +} + +// 03. Content +// ---------------------- + +.more-info-content { + + h3 { + color: $brand; + } + + .additional-info { + margin-bottom: $line-height; + } + + a:not(.button) { + text-decoration: underline; + } + + figure { + margin: 0; + text-align: center; + + figcaption { + font-size: $small-font-size; + font-style: italic; + } + } + + ul.features { + list-style-type: circle; + margin-left: $line-height; + + @include breakpoint(medium) { + margin: $line-height 0 $line-height $line-height*2.5; + } + + li { + margin-bottom: $line-height + } + } + + .section-content { + border-top: 1px solid $medium-gray; + padding-bottom: $line-height*2; + padding-top: $line-height*2; + + &:first-child { + border-top: 0; + padding-top: 0; + } + } +} + +// 04. Sidebar +// ---------------------- + +.more-info-sidebar { + + .sidebar-card { + border: 1px solid $border; + margin-bottom: $line-height; + padding: $line-height/2; + + &.light { + background: #ECF0F1; + border: 0; + } + } +} diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index c72b981c7..ad413925b 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -6,7 +6,8 @@ // 04. List participation // 05. Featured // 06. Budget -// 07. Proposals successfull +// 07. Proposals successful +// 08. Polls // // 01. Votes and supports @@ -297,7 +298,8 @@ .debate-show, .proposal-show, .investment-project-show, -.budget-investment-show { +.budget-investment-show, +.polls-show { p { word-wrap: break-word; @@ -455,7 +457,7 @@ } .bullet { - color: $border; + color: $text; } .investment-project-show p, .budget-investment-show p { @@ -769,7 +771,7 @@ // ------------ .featured-debates, .featured-proposals, -.proposals-ballot, .proposals-ballot-list { +.enquiries-list { padding: $line-height/2 0; @include breakpoint(medium) { @@ -974,6 +976,10 @@ &.social-share-button-google_plus:hover { color: #CE3E26; } + + &.social-share-button-telegram:hover { + color: #CE3E26; + } } } @@ -1202,7 +1208,7 @@ ul.ballot-list { } } -// 07. Proposals successfull +// 07. Proposals successful // ------------------------- .dark-heading { @@ -1235,7 +1241,7 @@ ul.ballot-list { } } -.featured-proposals-ballot-banner { +.featured-proposals-ballot-banner, .sucessfull-proposals-banner { background: #2D3E50 image-url("ballot_tiny.gif") no-repeat; background-position: 75% 0; position: relative; @@ -1259,10 +1265,10 @@ ul.ballot-list { } } -.featured-proposals-ballot-banner, -.successfull .panel { +.sucessfull-proposals-banner, +.successful .panel { - .icon-successfull { + .icon-successful { border-right: 60px solid #FFD200; border-top: 0; border-bottom: 60px solid transparent; @@ -1283,17 +1289,7 @@ ul.ballot-list { } } -.proposals-ballot-list { - - .proposal-sucessfull { - background: white; - border-top: 1px solid $border; - padding: $line-height 0; - position: relative; - } -} - -.successfull { +.successful { .panel { position: relative; @@ -1314,3 +1310,199 @@ ul.ballot-list { } } } + +// 08. Polls +// ---------------------- + +.dark-heading { + background: #2D3E50; + color: white; + + .title { + color: #92BA48; + } + + .button { + background: white; + color: $brand; + } + + .callout { + + &.warning a { + color: $color-warning; + } + + &.primary a { + color: $color-info; + } + + &.alert a { + color: $color-alert; + } + } + + .info { + background: #314253; + padding: $line-height; + + @include breakpoint(medium) { + border-top: rem-calc(6) solid #92BA48; + } + } + + a:not(.button) { + color: white; + text-decoration: underline; + } + + .back, .icon-angle-left { + color: white; + } + + &.polls-show-header { + min-height: $line-height*8; + } +} + +.poll, .poll-question { + background: white; + border-radius: rem-calc(6); + margin-bottom: $line-height/2; +} + +.poll { + padding: $line-height; + position: relative; + + .icon-poll-answer { + border-top: 0; + border-bottom: 60px solid transparent; + height: 0; + position: absolute; + right: 0; + top: 0; + width: 0; + + &.can-answer:after, + &.cant-answer:after, + &.not-logged-in:after, + &.already-answer:after, + &.unverified:after { + font-family: "icons" !important; + left: 34px; + position: absolute; + top: 5px; + } + + &.can-answer { + border-right: 60px solid $info-bg; + + &:after { + color: $color-info; + content: "\6c"; + } + } + + &.cant-answer { + border-right: 60px solid $alert-bg; + + &:after { + color: $color-alert; + content: "\74"; + } + } + + &.not-logged-in { + border-right: 60px solid $info-bg; + + &:after { + color: $color-info; + content: "\6f"; + } + } + + &.unverified { + border-right: 60px solid $warning-bg; + + &:after { + color: $color-warning; + content: "\6f"; + } + } + + &.already-answer { + border-right: 60px solid $success-bg; + + &:after { + color: $color-success; + content: "\59"; + } + } + } + + .dates { + color: $text-medium; + font-size: $small-font-size; + margin-bottom: $line-height/2; + } + + h4 { + font-size: rem-calc(30); + line-height: $line-height*1.5; + + a { + color: $text; + } + } +} + +h2.questions-callout { + font-size: $base-font-size; +} + +h3.section-title-divider { + border-bottom: 2px solid $brand; + color: $brand; + margin-bottom: $line-height; +} + +.poll-question { + padding: 0 $line-height; + + h3 { + padding-top: $line-height; + + a { + color: $text; + } + } +} + +.poll-question-answers { + + .button { + margin-right: $line-height/4; + min-width: rem-calc(168); + + &.answered { + background: #F4F8EC; + border: 2px solid #92BA48; + color: $text; + position: relative; + + &:after { + background: #92BA48; + border-radius: rem-calc(20); + content: "\6c"; + color: white; + font-family: "icons" !important; + font-size: rem-calc(12); + padding: $line-height/4; + position: absolute; + right: -6px; + top: -6px; + } + } + } +} diff --git a/app/controllers/admin/poll/booth_assignments_controller.rb b/app/controllers/admin/poll/booth_assignments_controller.rb new file mode 100644 index 000000000..f81382bf8 --- /dev/null +++ b/app/controllers/admin/poll/booth_assignments_controller.rb @@ -0,0 +1,66 @@ +class Admin::Poll::BoothAssignmentsController < Admin::BaseController + + before_action :load_poll, except: [:create, :destroy] + + def index + @booth_assignments = @poll.booth_assignments.includes(:booth).order('poll_booths.name').page(params[:page]).per(50) + end + + def search_booths + load_search + @booths = ::Poll::Booth.search(@search) + respond_to do |format| + format.js + end + end + + def show + @booth_assignment = @poll.booth_assignments.includes(:recounts, :final_recounts, :voters, officer_assignments: [officer: [:user]]).find(params[:id]) + @voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date} + end + + def create + @booth_assignment = ::Poll::BoothAssignment.new(poll_id: booth_assignment_params[:poll_id], booth_id: booth_assignment_params[:booth_id]) + + if @booth_assignment.save + notice = t("admin.poll_booth_assignments.flash.create") + else + notice = t("admin.poll_booth_assignments.flash.error_create") + end + redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + end + + def destroy + @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) + + if @booth_assignment.destroy + notice = t("admin.poll_booth_assignments.flash.destroy") + else + notice = t("admin.poll_booth_assignments.flash.error_destroy") + end + redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + end + + private + + def load_booth_assignment + @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) + end + + def booth_assignment_params + params.permit(:booth_id, :poll_id) + end + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/booths_controller.rb b/app/controllers/admin/poll/booths_controller.rb new file mode 100644 index 000000000..ff3700436 --- /dev/null +++ b/app/controllers/admin/poll/booths_controller.rb @@ -0,0 +1,39 @@ +class Admin::Poll::BoothsController < Admin::BaseController + load_and_authorize_resource class: 'Poll::Booth' + + def index + @booths = @booths.order(name: :asc).page(params[:page]) + end + + def show + end + + def new + end + + def create + if @booth.save + redirect_to admin_booths_path, notice: t("flash.actions.create.poll_booth") + else + render :new + end + end + + def edit + end + + def update + if @booth.update(booth_params) + redirect_to admin_booth_path(@booth), notice: t("flash.actions.update.poll_booth") + else + render :edit + end + end + + private + + def booth_params + params.require(:poll_booth).permit(:name, :location) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/officer_assignments_controller.rb b/app/controllers/admin/poll/officer_assignments_controller.rb new file mode 100644 index 000000000..f37ef9ab8 --- /dev/null +++ b/app/controllers/admin/poll/officer_assignments_controller.rb @@ -0,0 +1,92 @@ +class Admin::Poll::OfficerAssignmentsController < Admin::BaseController + + before_action :load_poll + before_action :redirect_if_blank_required_params, only: [:by_officer] + before_action :load_booth_assignment, only: [:create] + + def index + @officers = ::Poll::Officer. + includes(:user). + order('users.username'). + where( + id: @poll.officer_assignments.select(:officer_id).distinct.map(&:officer_id) + ).page(params[:page]).per(50) + end + + def by_officer + @poll = ::Poll.includes(:booths).find(params[:poll_id]) + @officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id]) + @officer_assignments = ::Poll::OfficerAssignment. + joins(:booth_assignment). + includes(:recount, :final_recounts, booth_assignment: :booth). + where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id). + order(:date) + end + + def search_officers + load_search + @officers = User.joins(:poll_officer).search(@search).order(username: :asc) + + respond_to do |format| + format.js + end + end + + def create + @officer_assignment = ::Poll::OfficerAssignment.new(booth_assignment: @booth_assignment, + officer_id: create_params[:officer_id], + date: create_params[:date]) + @officer_assignment.final = true if @officer_assignment.date > @booth_assignment.poll.ends_at.to_date + + if @officer_assignment.save + notice = t("admin.poll_officer_assignments.flash.create") + else + notice = t("admin.poll_officer_assignments.flash.error_create") + end + redirect_to by_officer_admin_poll_officer_assignments_path(poll_id: create_params[:poll_id], officer_id: create_params[:officer_id]), notice: notice + end + + def destroy + @officer_assignment = ::Poll::OfficerAssignment.includes(:booth_assignment).find(params[:id]) + + if @officer_assignment.destroy + notice = t("admin.poll_officer_assignments.flash.destroy") + else + notice = t("admin.poll_officer_assignments.flash.error_destroy") + end + redirect_to by_officer_admin_poll_officer_assignments_path(poll_id: @officer_assignment.poll_id, officer_id: @officer_assignment.officer_id), notice: notice + end + + private + + def officer_assignment_params + params.permit(:officer_id) + end + + def create_params + params.permit(:poll_id, :booth_id, :date, :officer_id) + end + + def load_booth_assignment + @booth_assignment = ::Poll::BoothAssignment.includes(:poll).find_by(poll_id: create_params[:poll_id], booth_id: create_params[:booth_id]) + end + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end + + def redirect_if_blank_required_params + if officer_assignment_params[:officer_id].blank? + redirect_to admin_poll_path(@poll) + end + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/officers_controller.rb b/app/controllers/admin/poll/officers_controller.rb new file mode 100644 index 000000000..2641a12b5 --- /dev/null +++ b/app/controllers/admin/poll/officers_controller.rb @@ -0,0 +1,39 @@ +class Admin::Poll::OfficersController < Admin::BaseController + load_and_authorize_resource :officer, class: "Poll::Officer", except: [:edit, :show] + + def index + @officers = @officers.page(params[:page]) + end + + def search + @user = User.find_by(email: params[:email]) + + respond_to do |format| + if @user + @officer = Poll::Officer.find_or_initialize_by(user: @user) + format.js + else + format.js { render "user_not_found" } + end + end + end + + def create + @officer.user_id = params[:user_id] + @officer.save + + redirect_to admin_officers_path + end + + def destroy + @officer.destroy + redirect_to admin_officers_path + end + + def show + end + + def edit + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb new file mode 100644 index 000000000..eda5736b0 --- /dev/null +++ b/app/controllers/admin/poll/polls_controller.rb @@ -0,0 +1,86 @@ +class Admin::Poll::PollsController < Admin::BaseController + load_and_authorize_resource + + before_action :load_search, only: [:search_booths, :search_questions, :search_officers] + before_action :load_geozones, only: [:new, :create, :edit, :update] + + def index + end + + def show + @poll = Poll.includes(:questions). + order('poll_questions.title'). + find(params[:id]) + end + + def new + end + + def create + if @poll.save + redirect_to [:admin, @poll], notice: t("flash.actions.create.poll") + else + render :new + end + end + + def edit + end + + def update + if @poll.update(poll_params) + redirect_to [:admin, @poll], notice: t("flash.actions.update.poll") + else + render :edit + end + end + + def add_question + question = ::Poll::Question.find(params[:question_id]) + + if question.present? + @poll.questions << question + notice = t("admin.polls.flash.question_added") + else + notice = t("admin.polls.flash.error_on_question_added") + end + redirect_to admin_poll_path(@poll), notice: notice + end + + def remove_question + question = ::Poll::Question.find(params[:question_id]) + + if @poll.questions.include? question + @poll.questions.delete(question) + notice = t("admin.polls.flash.question_removed") + else + notice = t("admin.polls.flash.error_on_question_removed") + end + redirect_to admin_poll_path(@poll), notice: notice + end + + def search_questions + @questions = ::Poll::Question.where("poll_id IS ? OR poll_id != ?", nil, @poll.id).search({search: @search}).order(title: :asc) + respond_to do |format| + format.js + end + end + + private + def load_geozones + @geozones = Geozone.all.order(:name) + end + + def poll_params + params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, geozone_ids: []) + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb new file mode 100644 index 000000000..ab7297a95 --- /dev/null +++ b/app/controllers/admin/poll/questions_controller.rb @@ -0,0 +1,64 @@ +class Admin::Poll::QuestionsController < Admin::BaseController + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: 'Poll::Question' + + def index + @polls = Poll.all + @search = search_params[:search] + + @questions = @questions.search(search_params).page(params[:page]).order("created_at DESC") + + @proposals = Proposal.successful.sort_by_confidence_score + end + + def new + @polls = Poll.all + @question.valid_answers = I18n.t('poll_questions.default_valid_answers') + proposal = Proposal.find(params[:proposal_id]) if params[:proposal_id].present? + @question.copy_attributes_from_proposal(proposal) + end + + def create + @question.author = @question.proposal.try(:author) || current_user + + if @question.save + redirect_to admin_question_path(@question) + else + render :new + end + end + + def show + end + + def edit + end + + def update + if @question.update(question_params) + redirect_to admin_question_path(@question), notice: t("flash.actions.save_changes.notice") + else + render :edit + end + end + + def destroy + if @question.destroy + notice = "Question destroyed succesfully" + else + notice = t("flash.actions.destroy.error") + end + redirect_to admin_questions_path, notice: notice + end + + private + + def question_params + params.require(:poll_question).permit(:poll_id, :title, :question, :description, :proposal_id, :valid_answers) + end + + def search_params + params.permit(:poll_id, :search) + end + +end diff --git a/app/controllers/admin/poll/recounts_controller.rb b/app/controllers/admin/poll/recounts_controller.rb new file mode 100644 index 000000000..fec546d79 --- /dev/null +++ b/app/controllers/admin/poll/recounts_controller.rb @@ -0,0 +1,16 @@ +class Admin::Poll::RecountsController < Admin::BaseController + before_action :load_poll + + def index + @booth_assignments = @poll.booth_assignments. + includes(:booth, :recounts, :final_recounts, :voters). + order("poll_booths.name"). + page(params[:page]).per(50) + end + + private + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end +end \ No newline at end of file diff --git a/app/controllers/admin/poll/results_controller.rb b/app/controllers/admin/poll/results_controller.rb new file mode 100644 index 000000000..2c5bbba27 --- /dev/null +++ b/app/controllers/admin/poll/results_controller.rb @@ -0,0 +1,13 @@ +class Admin::Poll::ResultsController < Admin::BaseController + before_action :load_poll + + def index + @partial_results = @poll.partial_results + end + + private + + def load_poll + @poll = ::Poll.includes(:questions).find(params[:poll_id]) + end +end \ No newline at end of file diff --git a/app/controllers/admin/site_customization/base_controller.rb b/app/controllers/admin/site_customization/base_controller.rb new file mode 100644 index 000000000..18422f66e --- /dev/null +++ b/app/controllers/admin/site_customization/base_controller.rb @@ -0,0 +1,10 @@ +class Admin::SiteCustomization::BaseController < Admin::BaseController + helper_method :namespace + + private + + def namespace + "admin" + end + +end diff --git a/app/controllers/admin/site_customization/content_blocks_controller.rb b/app/controllers/admin/site_customization/content_blocks_controller.rb new file mode 100644 index 000000000..2f0843ccc --- /dev/null +++ b/app/controllers/admin/site_customization/content_blocks_controller.rb @@ -0,0 +1,40 @@ +class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomization::BaseController + load_and_authorize_resource :content_block, class: "SiteCustomization::ContentBlock" + + def index + @content_blocks = SiteCustomization::ContentBlock.order(:name, :locale) + end + + def create + if @content_block.save + redirect_to admin_site_customization_content_blocks_path, notice: t('admin.site_customization.content_blocks.create.notice') + else + flash.now[:error] = t('admin.site_customization.content_blocks.create.error') + render :new + end + end + + def update + if @content_block.update(content_block_params) + redirect_to admin_site_customization_content_blocks_path, notice: t('admin.site_customization.content_blocks.update.notice') + else + flash.now[:error] = t('admin.site_customization.content_blocks.update.error') + render :edit + end + end + + def destroy + @content_block.destroy + redirect_to admin_site_customization_content_blocks_path, notice: t('admin.site_customization.content_blocks.destroy.notice') + end + + private + + def content_block_params + params.require(:site_customization_content_block).permit( + :name, + :locale, + :body + ) + end +end diff --git a/app/controllers/admin/site_customization/images_controller.rb b/app/controllers/admin/site_customization/images_controller.rb new file mode 100644 index 000000000..c9f318f41 --- /dev/null +++ b/app/controllers/admin/site_customization/images_controller.rb @@ -0,0 +1,43 @@ +class Admin::SiteCustomization::ImagesController < Admin::SiteCustomization::BaseController + load_and_authorize_resource :image, class: "SiteCustomization::Image" + + def index + @images = SiteCustomization::Image.all_images + end + + def update + if params[:site_customization_image].nil? + redirect_to admin_site_customization_images_path + return + end + + if @image.update(image_params) + redirect_to admin_site_customization_images_path, notice: t('admin.site_customization.images.update.notice') + else + flash.now[:error] = t('admin.site_customization.images.update.error') + + @images = SiteCustomization::Image.all_images + idx = @images.index {|e| e.name == @image.name } + @images[idx] = @image + + render :index + end + end + + def destroy + @image.image = nil + if @image.save + redirect_to admin_site_customization_images_path, notice: t('admin.site_customization.images.destroy.notice') + else + redirect_to admin_site_customization_images_path, notice: t('admin.site_customization.images.destroy.error') + end + end + + private + + def image_params + params.require(:site_customization_image).permit( + :image + ) + end +end diff --git a/app/controllers/admin/site_customization/pages_controller.rb b/app/controllers/admin/site_customization/pages_controller.rb new file mode 100644 index 000000000..4d92a6a1e --- /dev/null +++ b/app/controllers/admin/site_customization/pages_controller.rb @@ -0,0 +1,44 @@ +class Admin::SiteCustomization::PagesController < Admin::SiteCustomization::BaseController + load_and_authorize_resource :page, class: "SiteCustomization::Page" + + def index + @pages = SiteCustomization::Page.order('slug').page(params[:page]) + end + + def create + if @page.save + redirect_to admin_site_customization_pages_path, notice: t('admin.site_customization.pages.create.notice') + else + flash.now[:error] = t('admin.site_customization.pages.create.error') + render :new + end + end + + def update + if @page.update(page_params) + redirect_to admin_site_customization_pages_path, notice: t('admin.site_customization.pages.update.notice') + else + flash.now[:error] = t('admin.site_customization.pages.update.error') + render :edit + end + end + + def destroy + @page.destroy + redirect_to admin_site_customization_pages_path, notice: t('admin.site_customization.pages.destroy.notice') + end + + private + + def page_params + params.require(:site_customization_page).permit( + :slug, + :title, + :subtitle, + :content, + :more_info_flag, + :print_content_flag, + :status + ) + end +end diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb index ef5e60dcb..ee69ac809 100644 --- a/app/controllers/concerns/commentable_actions.rb +++ b/app/controllers/concerns/commentable_actions.rb @@ -129,16 +129,16 @@ module CommentableActions when '4' 1.year.ago else - Date.parse(params[:advanced_search][:date_min]) rescue nil + Date.parse(params[:advanced_search][:date_min]) rescue 100.years.ago end end def search_finish_date - params[:advanced_search][:date_max].try(:to_date) || Date.today + (params[:advanced_search][:date_max].to_date rescue Date.today) || Date.today end def search_date_range - search_start_date.beginning_of_day..search_finish_date.end_of_day + [100.years.ago, search_start_date].max.beginning_of_day..[search_finish_date, Date.today].min.end_of_day end def set_search_order diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index 8262f014a..6d17eb749 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -22,7 +22,7 @@ class DebatesController < ApplicationController def index_customization @featured_debates = @debates.featured - @proposal_successfull_exists = Proposal.successfull.exists? + @proposal_successfull_exists = Proposal.successful.exists? end def show diff --git a/app/controllers/management/budgets_controller.rb b/app/controllers/management/budgets_controller.rb index c5cfdee76..dd7259c99 100644 --- a/app/controllers/management/budgets_controller.rb +++ b/app/controllers/management/budgets_controller.rb @@ -7,6 +7,11 @@ class Management::BudgetsController < Management::BaseController def create_investments @budgets = Budget.accepting.order(created_at: :desc).page(params[:page]) + + if current_manager_administrator? + @budgets += Budget.reviewing.order(created_at: :desc) + + Budget.selecting.order(created_at: :desc) + end end def support_investments @@ -23,4 +28,8 @@ class Management::BudgetsController < Management::BaseController check_verified_user t("management.budget_investments.alert.unverified_user") end + def current_manager_administrator? + session[:manager]["login"].match("admin") + end + end diff --git a/app/controllers/management/users_controller.rb b/app/controllers/management/users_controller.rb index 53004a838..8d3d148fa 100644 --- a/app/controllers/management/users_controller.rb +++ b/app/controllers/management/users_controller.rb @@ -32,7 +32,7 @@ class Management::UsersController < Management::BaseController private def user_params - params.require(:user).permit(:document_type, :document_number, :username, :email) + params.require(:user).permit(:document_type, :document_number, :username, :email, :date_of_birth) end def destroy_session diff --git a/app/controllers/officing/base_controller.rb b/app/controllers/officing/base_controller.rb new file mode 100644 index 000000000..97ef23d30 --- /dev/null +++ b/app/controllers/officing/base_controller.rb @@ -0,0 +1,12 @@ +class Officing::BaseController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + before_action :verify_officer + + skip_authorization_check + + def verify_officer + raise CanCan::AccessDenied unless current_user.try(:poll_officer?) || current_user.try(:administrator?) + end +end \ No newline at end of file diff --git a/app/controllers/officing/dashboard_controller.rb b/app/controllers/officing/dashboard_controller.rb new file mode 100644 index 000000000..4d80a974a --- /dev/null +++ b/app/controllers/officing/dashboard_controller.rb @@ -0,0 +1,6 @@ +class Officing::DashboardController < Officing::BaseController + + def index + end + +end diff --git a/app/controllers/officing/final_recounts_controller.rb b/app/controllers/officing/final_recounts_controller.rb new file mode 100644 index 000000000..e381240e7 --- /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 + msg = { notice: t("officing.final_recounts.flash.create") } + else + msg = { alert: t("officing.final_recounts.flash.error_create") } + end + redirect_to new_officing_poll_final_recount_path(@poll), msg + 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), alert: 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 new file mode 100644 index 000000000..e122284ec --- /dev/null +++ b/app/controllers/officing/polls_controller.rb @@ -0,0 +1,15 @@ +class Officing::PollsController < Officing::BaseController + + def index + @polls = current_user.poll_officer? ? current_user.poll_officer.voting_days_assigned_polls : [] + @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 new file mode 100644 index 000000000..1c66e57fd --- /dev/null +++ b/app/controllers/officing/recounts_controller.rb @@ -0,0 +1,46 @@ +class Officing::RecountsController < Officing::BaseController + before_action :load_poll + before_action :load_officer_assignment, only: :create + + def new + @officer_assignments = ::Poll::OfficerAssignment. + includes(:recount, booth_assignment: :booth). + joins(:booth_assignment). + voting_days. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + @recounted = @officer_assignments.select {|oa| oa.recount.present?}.reverse + end + + def create + @recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, date: @officer_assignment.date) + @recount.officer_assignment_id = @officer_assignment.id + @recount.count = recount_params[:count] + + if @recount.save + msg = { notice: t("officing.recounts.flash.create") } + else + msg = { alert: t("officing.recounts.flash.error_create") } + end + redirect_to new_officing_poll_recount_path(@poll), msg + end + + private + def load_poll + @poll = Poll.find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.find_by(id: recount_params[:officer_assignment_id]) + if @officer_assignment.blank? + redirect_to new_officing_poll_recount_path(@poll), alert: t("officing.recounts.flash.error_create") + end + end + + def recount_params + params.permit(:officer_assignment_id, :count) + end + +end \ No newline at end of file diff --git a/app/controllers/officing/residence_controller.rb b/app/controllers/officing/residence_controller.rb new file mode 100644 index 000000000..9f24d3a90 --- /dev/null +++ b/app/controllers/officing/residence_controller.rb @@ -0,0 +1,37 @@ +class Officing::ResidenceController < Officing::BaseController + + before_action :load_officer_assignment + before_action :validate_officer_assignment, only: :create + + def new + @residence = Officing::Residence.new + end + + def create + @residence = Officing::Residence.new(residence_params.merge(officer: current_user.poll_officer)) + if @residence.save + redirect_to new_officing_voter_path(id: @residence.user.id), notice: t("officing.residence.flash.create") + else + render :new + end + end + + private + + def residence_params + params.require(:residence).permit(:document_number, :document_type, :year_of_birth) + end + + def load_officer_assignment + @officer_assignments = current_user.poll_officer. + officer_assignments. + voting_days. + where(date: Time.current.to_date) + end + + def validate_officer_assignment + if @officer_assignments.blank? + redirect_to officing_root_path, notice: t("officing.residence.flash.not_allowed") + end + end +end diff --git a/app/controllers/officing/results_controller.rb b/app/controllers/officing/results_controller.rb new file mode 100644 index 000000000..b6377e25b --- /dev/null +++ b/app/controllers/officing/results_controller.rb @@ -0,0 +1,141 @@ +class Officing::ResultsController < Officing::BaseController + before_action :load_poll + + before_action :load_officer_assignments, only: :new + before_action :load_partial_results, only: :new + + before_action :load_officer_assignment, only: :create + before_action :check_booth_and_date, only: :create + before_action :build_results, only: :create + + def new + end + + def create + @results.each { |result| result.save! } + + notice = t("officing.results.flash.create") + redirect_to new_officing_poll_result_path(@poll), notice: notice + end + + def index + @booth_assignment = ::Poll::BoothAssignment.includes(:booth).find(index_params[:booth_assignment_id]) + if current_user.poll_officer.officer_assignments.final. + where(booth_assignment_id: @booth_assignment.id).exists? + + @partial_results = ::Poll::PartialResult.includes(:question). + where(booth_assignment_id: index_params[:booth_assignment_id]). + where(date: index_params[:date]) + @whites = ::Poll::WhiteResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) + @nulls = ::Poll::NullResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) + end + end + + private + + def check_booth_and_date + if @officer_assignment.blank? + go_back_to_new(t("officing.results.flash.error_wrong_booth")) + elsif results_params[:date].blank? || + Date.parse(results_params[:date]) < @poll.starts_at.to_date || + Date.parse(results_params[:date]) > @poll.ends_at.to_date + go_back_to_new(t("officing.results.flash.error_wrong_date")) + end + end + + def build_results + @results = [] + + params[:questions].each_pair do |question_id, results| + question = @poll.questions.find(question_id) + go_back_to_new if question.blank? + + results.each_pair do |answer_index, count| + if count.present? + answer = question.valid_answers[answer_index.to_i] + go_back_to_new if question.blank? + + partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date], + question_id: question_id, + answer: answer) + partial_result.officer_assignment_id = @officer_assignment.id + partial_result.amount = count.to_i + partial_result.author = current_user + partial_result.origin = 'booth' + @results << partial_result + end + end + end + + build_white_results + build_null_results + end + + def build_white_results + if results_params[:whites].present? + white_result = ::Poll::WhiteResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date]) + white_result.officer_assignment_id = @officer_assignment.id + white_result.amount = results_params[:whites].to_i + white_result.author = current_user + white_result.origin = 'booth' + @results << white_result + end + end + + def build_null_results + if results_params[:nulls].present? + null_result = ::Poll::NullResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date]) + null_result.officer_assignment_id = @officer_assignment.id + null_result.amount = results_params[:nulls].to_i + null_result.author = current_user + null_result.origin = 'booth' + @results << null_result + end + end + + def go_back_to_new(alert = nil) + params[:d] = results_params[:date] + params[:oa] = results_params[:officer_assignment_id] + flash.now[:alert] = (alert || t("officing.results.flash.error_create")) + load_officer_assignments + load_partial_results + render :new + end + + def load_poll + @poll = ::Poll.expired.includes(:questions).find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.final.find_by(id: results_params[:officer_assignment_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.id). + order(date: :asc) + end + + def load_partial_results + if @officer_assignments.present? + @partial_results = ::Poll::PartialResult.where(officer_assignment_id: @officer_assignments.map(&:id)).order(:booth_assignment_id, :date) + end + end + + def results_params + params.permit(:officer_assignment_id, :date, :questions, :whites, :nulls) + end + + def index_params + params.permit(:booth_assignment_id, :date) + end + +end diff --git a/app/controllers/officing/voters_controller.rb b/app/controllers/officing/voters_controller.rb new file mode 100644 index 000000000..dee1e00bd --- /dev/null +++ b/app/controllers/officing/voters_controller.rb @@ -0,0 +1,25 @@ +class Officing::VotersController < Officing::BaseController + respond_to :html, :js + + def new + @user = User.find(params[:id]) + @polls = Poll.answerable_by(@user) + end + + def create + @poll = Poll.find(voter_params[:poll_id]) + @user = User.find(voter_params[:user_id]) + @voter = Poll::Voter.new(document_type: @user.document_type, + document_number: @user.document_number, + user: @user, + poll: @poll) + @voter.save! + end + + private + + def voter_params + params.require(:voter).permit(:poll_id, :user_id) + end + +end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 03f62925a..81d6e8dbd 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -2,7 +2,11 @@ class PagesController < ApplicationController skip_authorization_check def show - render action: params[:id] + if @custom_page = SiteCustomization::Page.published.find_by(slug: params[:id]) + render action: :custom_page + else + render action: params[:id] + end rescue ActionView::MissingTemplate head 404 end diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb new file mode 100644 index 000000000..1849dff97 --- /dev/null +++ b/app/controllers/polls/questions_controller.rb @@ -0,0 +1,27 @@ +class Polls::QuestionsController < ApplicationController + + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: 'Poll::Question' + + has_orders %w{most_voted newest oldest}, only: :show + + def show + @commentable = @question.proposal.present? ? @question.proposal : @question + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + + question_answer = @question.answers.where(author_id: current_user.try(:id)).first + @answers_by_question_id = {@question.id => question_answer.try(:answer)} + end + + def answer + answer = @question.answers.find_or_initialize_by(author: current_user) + + answer.answer = params[:answer] + answer.save! + answer.record_voter_participation + + @answers_by_question_id = {@question.id => params[:answer]} + end + +end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb new file mode 100644 index 000000000..41a038b46 --- /dev/null +++ b/app/controllers/polls_controller.rb @@ -0,0 +1,23 @@ +class PollsController < ApplicationController + + load_and_authorize_resource + + has_filters %w{current expired incoming} + + ::Poll::Answer # trigger autoload + + def index + @polls = @polls.send(@current_filter).includes(:geozones).sort_for_list.page(params[:page]) + end + + def show + @questions = @poll.questions.for_render.sort_for_list + + @answers_by_question_id = {} + poll_answers = ::Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id)) + poll_answers.each do |answer| + @answers_by_question_id[answer.question_id] = answer.answer + end + end + +end diff --git a/app/controllers/proposal_ballots_controller.rb b/app/controllers/proposal_ballots_controller.rb deleted file mode 100644 index 4171fcda8..000000000 --- a/app/controllers/proposal_ballots_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ProposalBallotsController < ApplicationController - skip_authorization_check - - def index - @proposal_ballots = Proposal.successfull.sort_by_confidence_score - end - -end \ No newline at end of file diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index d0ff9551e..daec4a051 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -28,8 +28,8 @@ class ProposalsController < ApplicationController def index_customization discard_archived load_retired - load_proposal_ballots - load_featured unless @proposal_successfull_exists + load_successful_proposals + load_featured unless @proposal_successful_exists end def vote @@ -103,8 +103,8 @@ class ProposalsController < ApplicationController end end - def load_proposal_ballots - @proposal_successfull_exists = Proposal.successfull.exists? + def load_successful_proposals + @proposal_successful_exists = Proposal.successful.exists? end end diff --git a/app/controllers/valuation/budget_investments_controller.rb b/app/controllers/valuation/budget_investments_controller.rb index 666fac74a..ae747464e 100644 --- a/app/controllers/valuation/budget_investments_controller.rb +++ b/app/controllers/valuation/budget_investments_controller.rb @@ -26,6 +26,7 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController @investment.send_unfeasible_email end + Activity.log(current_user, :valuate, @investment) redirect_to valuation_budget_budget_investment_path(@budget, @investment), notice: t('valuation.budget_investments.notice.valuate') else render action: :edit diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 62d31cd05..3f0d4db4b 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -4,6 +4,38 @@ module AdminHelper render "/#{namespace}/menu" end + def namespaced_root_path + "/#{namespace}" + end + + def namespaced_header_title + t("#{namespace}.header.title") + end + + def menu_tags? + ["tags"].include? controller_name + end + + def menu_moderated_content? + ["proposals", "debates", "comments", "users"].include? controller_name + end + + def menu_budget? + ["spending_proposals"].include? controller_name + end + + def menu_polls? + ["polls", "questions", "officers", "booths", "officer_assignments", "booth_assignments", "recounts", "results"].include? controller_name + end + + def menu_profiles? + ["organizations", "officials", "moderators", "valuators", "managers"].include? controller_name + end + + def menu_banners? + ["banners"].include? controller_name + end + def official_level_options options = [["", 0]] (1..5).each do |i| @@ -16,10 +48,14 @@ module AdminHelper Administrator.all.order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] } end + def admin_submit_action(resource) + resource.persisted? ? "edit" : "new" + end + private def namespace - controller.class.parent.name.downcase + controller.class.parent.name.downcase.gsub("::", "/") end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 208ed3d3d..0c23c0d22 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -47,4 +47,12 @@ module ApplicationHelper "".html_safe + t("shared.back") end end + + def image_path_for(filename) + SiteCustomization::Image.image_path_for(filename) || filename + end + + def content_block(name, locale) + SiteCustomization::ContentBlock.block_for(name, locale) + end end diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb index 4b9dfd311..3a07f0393 100644 --- a/app/helpers/budgets_helper.rb +++ b/app/helpers/budgets_helper.rb @@ -10,7 +10,7 @@ module BudgetsHelper def namespaced_budget_investment_path(investment, options={}) case namespace - when "management::budgets" + when "management/budgets" management_budget_investment_path(investment.budget, investment, options) else budget_investment_path(investment.budget, investment, options) @@ -19,7 +19,7 @@ module BudgetsHelper def namespaced_budget_investment_vote_path(investment, options={}) case namespace - when "management::budgets" + when "management/budgets" vote_management_budget_investment_path(investment.budget, investment, options) else vote_budget_investment_path(investment.budget, investment, options) diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index b9e69ddf9..df6e57b61 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -23,6 +23,8 @@ module CommentsHelper def commentable_path(comment) if comment.commentable_type == "Budget::Investment" budget_investment_path(comment.commentable.budget_id, comment.commentable) + elsif comment.commentable_type == "Poll::Question" + question_path(comment.commentable) else comment.commentable end diff --git a/app/helpers/embed_videos_helper.rb b/app/helpers/embed_videos_helper.rb index 8633549f4..22b1d878f 100644 --- a/app/helpers/embed_videos_helper.rb +++ b/app/helpers/embed_videos_helper.rb @@ -2,6 +2,7 @@ module EmbedVideosHelper def embedded_video_code link = @proposal.video_url + title = t('proposals.show.embed_video_title', proposal: @proposal.title) if link.match(/vimeo.*/) server = "Vimeo" elsif link.match(/youtu*.*/) @@ -21,7 +22,7 @@ module EmbedVideosHelper end if match and match[2] - '' + '' else '' end diff --git a/app/helpers/officers_helper.rb b/app/helpers/officers_helper.rb new file mode 100644 index 000000000..f29bcf728 --- /dev/null +++ b/app/helpers/officers_helper.rb @@ -0,0 +1,7 @@ +module OfficersHelper + + def officer_label(officer) + truncate([officer.name, officer.email].compact.join(' - '), length: 100) + end + +end \ No newline at end of file diff --git a/app/helpers/officing_helper.rb b/app/helpers/officing_helper.rb new file mode 100644 index 000000000..2cfebf832 --- /dev/null +++ b/app/helpers/officing_helper.rb @@ -0,0 +1,36 @@ +module OfficingHelper + + def officer_assignments_select_options(officer_assignments) + options = [] + officer_assignments.each do |oa| + options << ["#{oa.booth_assignment.booth.name}: #{l(oa.date.to_date, format: :long)}", oa.id] + end + 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.sort! {|x,y| x[0]<=>y[0]} + options_for_select(options, params[:oa]) + end + + def recount_to_compare_with_final_recount(final_recount) + recount = final_recount.booth_assignment.recounts.select {|r| r.date == final_recount.date}.first + recount.present? ? recount.count : "-" + end + + def system_recount_to_compare_with_final_recount(final_recount) + final_recount.booth_assignment.voters.select {|v| v.created_at.to_date == final_recount.date}.size + end + + def answer_result_value(question_id, answer_index) + return nil if params.blank? + return nil if params[:questions].blank? + return nil if params[:questions][question_id.to_s].blank? + params[:questions][question_id.to_s][answer_index.to_s] + end + +end \ No newline at end of file diff --git a/app/helpers/poll_final_recounts_helper.rb b/app/helpers/poll_final_recounts_helper.rb new file mode 100644 index 000000000..e196cb65b --- /dev/null +++ b/app/helpers/poll_final_recounts_helper.rb @@ -0,0 +1,7 @@ +module PollFinalRecountsHelper + + def final_recount_for_date(final_recounts, date) + final_recounts.select {|f| f.date == date}.first + end + +end \ No newline at end of file diff --git a/app/helpers/poll_recounts_helper.rb b/app/helpers/poll_recounts_helper.rb new file mode 100644 index 000000000..c47402163 --- /dev/null +++ b/app/helpers/poll_recounts_helper.rb @@ -0,0 +1,15 @@ +module PollRecountsHelper + + def recount_for_date(recounts, date) + recounts.select {|r| r.date == date}.first + end + + def booth_assignment_sum_recounts(ba) + ba.recounts.any? ? ba.recounts.to_a.sum(&:count) : nil + end + + def booth_assignment_sum_final_recounts(ba) + ba.final_recounts.any? ? ba.final_recounts.to_a.sum(&:count) :nil + end + +end \ No newline at end of file diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb new file mode 100644 index 000000000..5425a6b52 --- /dev/null +++ b/app/helpers/polls_helper.rb @@ -0,0 +1,49 @@ +module PollsHelper + + def poll_select_options(include_all=nil) + options = @polls.collect {|poll| + [poll.name, current_path_with_query_params(poll_id: poll.id)] + } + options << all_polls if include_all + options_for_select(options, request.fullpath) + end + + def all_polls + [I18n.t("polls.all"), admin_questions_path] + end + + def poll_dates(poll) + if poll.starts_at.blank? || poll.ends_at.blank? + I18n.t("polls.no_dates") + else + I18n.t("polls.dates", open_at: l(poll.starts_at.to_date), closed_at: l(poll.ends_at.to_date)) + end + end + + def poll_dates_select_options(poll) + options = [] + (poll.starts_at.to_date..poll.ends_at.to_date).each do |date| + options << [l(date, format: :long), l(date)] + end + options_for_select(options, params[:d]) + end + + def poll_final_recount_option(poll) + final_date = poll.ends_at.to_date + 1.day + options_for_select([[I18n.t("polls.final_date"), l(final_date)]]) + end + + def poll_booths_select_options(poll) + options = [] + poll.booths.each do |booth| + options << [booth_name_with_location(booth), booth.id] + end + options_for_select(options) + end + + def booth_name_with_location(booth) + location = booth.location.blank? ? "" : " (#{booth.location})" + booth.name + location + end + +end \ No newline at end of file diff --git a/app/helpers/signature_sheets_helper.rb b/app/helpers/signature_sheets_helper.rb index acd75a5ab..8e75dccfd 100644 --- a/app/helpers/signature_sheets_helper.rb +++ b/app/helpers/signature_sheets_helper.rb @@ -2,7 +2,7 @@ module SignatureSheetsHelper def signable_options [[t("activerecord.models.proposal", count: 1), Proposal], - [t("activerecord.models.spending_proposal", count: 1), SpendingProposal]] + [t("activerecord.models.budget/investment", count: 1), Budget::Investment]] end end \ No newline at end of file diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 8e721b2b8..8df52e6eb 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -32,7 +32,7 @@ module Abilities can :mark_featured, Debate can :unmark_featured, Debate - can :comment_as_administrator, [Debate, Comment, Proposal, Budget::Investment] + can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment] can [:search, :create, :index, :destroy], ::Moderator can [:search, :create, :index, :summary], ::Valuator @@ -50,7 +50,20 @@ module Abilities can :create, Budget::ValuatorAssignment can [:search, :edit, :update, :create, :index, :destroy], Banner + can [:index, :create, :edit, :update, :destroy], Geozone + + can [:read, :create, :update, :destroy, :add_question, :remove_question, :search_booths, :search_questions, :search_officers], Poll + can [:read, :create, :update, :destroy], Poll::Booth + can [:search, :create, :index, :destroy], ::Poll::Officer + can [:create, :destroy], ::Poll::BoothAssignment + can [:create, :destroy], ::Poll::OfficerAssignment + can [:read, :create, :update], Poll::Question + can :destroy, Poll::Question # , comments_count: 0, votes_up: 0 + + can :manage, SiteCustomization::Page + can :manage, SiteCustomization::Image + can :manage, SiteCustomization::ContentBlock end end end diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 7ac6a12f4..6a8ef594c 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -53,6 +53,12 @@ module Abilities can :create, DirectMessage can :show, DirectMessage, sender_id: user.id + can :answer, Poll do |poll| + poll.answerable_by?(user) + end + can :answer, Poll::Question do |question| + question.answerable_by?(user) + end end can [:create, :show], ProposalNotification, proposal: { author_id: user.id } diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 98080aa4c..c2b6cd6d7 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -6,6 +6,8 @@ module Abilities can [:read, :map], Debate can [:read, :map, :summary], Proposal can :read, Comment + can :read, Poll + can :read, Poll::Question can [:read, :welcome], Budget can :read, Budget::Investment can :read, SpendingProposal diff --git a/app/models/abilities/moderator.rb b/app/models/abilities/moderator.rb index 52d838f9c..5740e302e 100644 --- a/app/models/abilities/moderator.rb +++ b/app/models/abilities/moderator.rb @@ -5,7 +5,7 @@ module Abilities def initialize(user) self.merge Abilities::Moderation.new(user) - can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment] + can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment, Poll::Question] end end end diff --git a/app/models/activity.rb b/app/models/activity.rb index 047ccb7dd..0fc35ad11 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -2,7 +2,7 @@ class Activity < ActiveRecord::Base belongs_to :actionable, -> { with_hidden }, polymorphic: true belongs_to :user, -> { with_hidden } - VALID_ACTIONS = %w( hide block restore ) + VALID_ACTIONS = %w( hide block restore valuate ) validates :action, inclusion: {in: VALID_ACTIONS} @@ -10,6 +10,7 @@ class Activity < ActiveRecord::Base scope :on_debates, -> { where(actionable_type: 'Debate') } scope :on_users, -> { where(actionable_type: 'User') } scope :on_comments, -> { where(actionable_type: 'Comment') } + scope :on_budget_investments, -> { where(actionable_type: 'Budget::Investment') } scope :for_render, -> { includes(user: [:moderator, :administrator]).includes(:actionable) } def self.log(user, action, actionable) diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb index 100433707..4bfd038c3 100644 --- a/app/models/budget/investment.rb +++ b/app/models/budget/investment.rb @@ -25,6 +25,7 @@ class Budget validates :description, presence: true validates :heading_id, presence: true validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required? + validates_presence_of :price, if: :price_required? validates :title, length: { in: 4..Budget::Investment.title_max_length } validates :description, length: { maximum: Budget::Investment.description_max_length } @@ -136,6 +137,10 @@ class Budget unfeasible? && valuation_finished? end + def price_required? + feasible? && valuation_finished? + end + def unfeasible_email_pending? unfeasible_email_sent_at.blank? && unfeasible? && valuation_finished? end @@ -225,7 +230,7 @@ class Budget def should_show_aside? (budget.selecting? && !unfeasible?) || (budget.balloting? && feasible?) || - (budget.valuating? && feasible?) + (budget.valuating? && !unfeasible?) end def should_show_votes? @@ -259,7 +264,7 @@ class Budget private def set_denormalized_ids - self.group_id ||= self.heading.try(:group_id) + self.group_id = self.heading.try(:group_id) if self.heading_id_changed? self.budget_id ||= self.heading.try(:group).try(:budget_id) end end diff --git a/app/models/comment.rb b/app/models/comment.rb index d4b1cc8e7..cd84a3578 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -10,7 +10,8 @@ class Comment < ActiveRecord::Base validates :body, presence: true validates :user, presence: true - validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment"] + + validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment", "Poll::Question"] validate :validate_body_length diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 4d717959e..147a37fbc 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -12,7 +12,7 @@ module Searchable }, ignoring: :accents, ranked_by: '(:tsearch)', - order_within_rank: "#{self.table_name}.cached_votes_up DESC" + order_within_rank: (self.column_names.include?('cached_votes_up') ? "#{self.table_name}.cached_votes_up DESC" : nil) } end diff --git a/app/models/failed_census_call.rb b/app/models/failed_census_call.rb index ac792d7b7..b7d60e63a 100644 --- a/app/models/failed_census_call.rb +++ b/app/models/failed_census_call.rb @@ -1,3 +1,4 @@ class FailedCensusCall < ActiveRecord::Base belongs_to :user, counter_cache: true + belongs_to :poll_officer, class_name: 'Poll::Officer', counter_cache: true end diff --git a/app/models/geozone.rb b/app/models/geozone.rb index 7e38ce97d..824879ec6 100644 --- a/app/models/geozone.rb +++ b/app/models/geozone.rb @@ -9,6 +9,10 @@ class Geozone < ActiveRecord::Base Geozone.pluck(:name) end + def self.city + where(name: 'city').first + end + def safe_to_destroy? Geozone.reflect_on_all_associations(:has_many).all? do |association| association.klass.where(geozone: self).empty? diff --git a/app/models/officing/residence.rb b/app/models/officing/residence.rb new file mode 100644 index 000000000..6343fc5f7 --- /dev/null +++ b/app/models/officing/residence.rb @@ -0,0 +1,126 @@ +class Officing::Residence + include ActiveModel::Model + include ActiveModel::Validations::Callbacks + + attr_accessor :user, :officer, :document_number, :document_type, :year_of_birth + + before_validation :call_census_api + + validates_presence_of :document_number + validates_presence_of :document_type + validates_presence_of :year_of_birth + + validate :allowed_age + validate :residence_in_madrid + + def initialize(attrs={}) + super + clean_document_number + end + + def save + return false unless valid? + + if user_exists? + self.user = find_user_by_document + self.user.update(verified_at: Time.current) + else + user_params = { + document_number: document_number, + document_type: document_type, + geozone: self.geozone, + date_of_birth: date_of_birth.to_datetime, + gender: gender, + residence_verified_at: Time.current, + verified_at: Time.current, + erased_at: Time.current, + password: random_password, + terms_of_service: '1', + email: nil + } + self.user = User.create!(user_params) + end + end + + def store_failed_census_call + FailedCensusCall.create({ + user: user, + document_number: document_number, + document_type: document_type, + year_of_birth: year_of_birth, + poll_officer: officer + }) + + end + + def user_exists? + find_user_by_document.present? + end + + def find_user_by_document + User.where(document_number: document_number, + document_type: document_type).first + end + + def residence_in_madrid + return if errors.any? + + unless residency_valid? + store_failed_census_call + errors.add(:residence_in_madrid, false) + end + end + + def allowed_age + return if errors[:year_of_birth].any? + return unless @census_api_response.valid? + + unless allowed_age? + errors.add(:year_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) + end + end + + def allowed_age? + Age.in_years(date_of_birth) >= User.minimum_required_age + end + + def geozone + Geozone.where(census_code: district_code).first + end + + def district_code + @census_api_response.district_code + end + + def gender + @census_api_response.gender + end + + def date_of_birth + @census_api_response.date_of_birth + end + + private + + def call_census_api + @census_api_response = CensusApi.new.call(document_type, document_number) + end + + def residency_valid? + @census_api_response.valid? && + @census_api_response.date_of_birth.year.to_s == year_of_birth.to_s + end + + def census_year_of_birth + @census_api_response.date_of_birth.year + end + + def clean_document_number + self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank? + end + + def random_password + (0...20).map { ('a'..'z').to_a[rand(26)] }.join + end + +end diff --git a/app/models/poll.rb b/app/models/poll.rb new file mode 100644 index 000000000..c6be3073a --- /dev/null +++ b/app/models/poll.rb @@ -0,0 +1,65 @@ +class Poll < ActiveRecord::Base + has_many :booth_assignments, class_name: "Poll::BoothAssignment" + has_many :booths, through: :booth_assignments + has_many :partial_results, through: :booth_assignments + has_many :white_results, through: :booth_assignments + has_many :null_results, through: :booth_assignments + has_many :voters + has_many :officer_assignments, through: :booth_assignments + has_many :officers, through: :officer_assignments + has_many :questions + + has_and_belongs_to_many :geozones + + validates :name, presence: true + + validate :date_range + + scope :current, -> { where('starts_at <= ? and ? <= ends_at', Time.current, Time.current) } + scope :incoming, -> { where('? < starts_at', Time.current) } + scope :expired, -> { where('ends_at < ?', Time.current) } + scope :published, -> { where('published = ?', true) } + scope :by_geozone_id, ->(geozone_id) { where(geozones: {id: geozone_id}.joins(:geozones)) } + + scope :sort_for_list, -> { order(:geozone_restricted, :starts_at, :name) } + + def current?(timestamp = DateTime.current) + starts_at <= timestamp && timestamp <= ends_at + end + + def incoming?(timestamp = DateTime.current) + timestamp < starts_at + end + + def expired?(timestamp = DateTime.current) + ends_at < timestamp + end + + def answerable_by?(user) + user.present? && + user.level_two_or_three_verified? && + current? && + (!geozone_restricted || geozone_ids.include?(user.geozone_id)) + end + + def self.answerable_by(user) + return none if user.nil? || user.unverified? + current.joins('LEFT JOIN "geozones_polls" ON "geozones_polls"."poll_id" = "polls"."id"') + .where('geozone_restricted = ? OR geozones_polls.geozone_id = ?', false, user.geozone_id) + end + + def votable_by?(user) + !document_has_voted?(user.document_number, user.document_type) + end + + def document_has_voted?(document_number, document_type) + voters.where(document_number: document_number, document_type: document_type).exists? + end + + def date_range + unless starts_at.present? && ends_at.present? && starts_at <= ends_at + errors.add(:starts_at, I18n.t('errors.messages.invalid_date_range')) + end + end + +end diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb new file mode 100644 index 000000000..52fb11469 --- /dev/null +++ b/app/models/poll/answer.rb @@ -0,0 +1,19 @@ +class Poll::Answer < ActiveRecord::Base + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + + delegate :poll, :poll_id, to: :question + + validates :question, presence: true + validates :author, presence: true + validates :answer, presence: true + validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + scope :by_question, -> (question_id) { where(question_id: question_id) } + + def record_voter_participation + Poll::Voter.create!(user: author, poll: poll) + end +end \ No newline at end of file diff --git a/app/models/poll/booth.rb b/app/models/poll/booth.rb new file mode 100644 index 000000000..c7fb63efc --- /dev/null +++ b/app/models/poll/booth.rb @@ -0,0 +1,13 @@ +class Poll + class Booth < ActiveRecord::Base + has_many :booth_assignments, class_name: "Poll::BoothAssignment" + has_many :polls, through: :booth_assignments + + validates :name, presence: true, uniqueness: true + + def self.search(terms) + return Booth.none if terms.blank? + Booth.where("name ILIKE ? OR location ILIKE ?", "%#{terms}%", "%#{terms}%") + end + end +end \ No newline at end of file diff --git a/app/models/poll/booth_assignment.rb b/app/models/poll/booth_assignment.rb new file mode 100644 index 000000000..0519fffa6 --- /dev/null +++ b/app/models/poll/booth_assignment.rb @@ -0,0 +1,15 @@ +class Poll + class BoothAssignment < ActiveRecord::Base + belongs_to :booth + belongs_to :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 + has_many :partial_results + has_many :white_results + has_many :null_results + end +end diff --git a/app/models/poll/final_recount.rb b/app/models/poll/final_recount.rb new file mode 100644 index 000000000..6ebf5eede --- /dev/null +++ b/app/models/poll/final_recount.rb @@ -0,0 +1,19 @@ +class Poll + class FinalRecount < ActiveRecord::Base + belongs_to :booth_assignment, class_name: "Poll::BoothAssignment" + belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" + + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :count, presence: true, numericality: {only_integer: true} + + before_save :update_logs + + def update_logs + if self.count_changed? && self.count_was.present? + self.count_log += ":#{self.count_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + end + end + end +end \ No newline at end of file diff --git a/app/models/poll/null_result.rb b/app/models/poll/null_result.rb new file mode 100644 index 000000000..222432c7f --- /dev/null +++ b/app/models/poll/null_result.rb @@ -0,0 +1,23 @@ +class Poll::NullResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :author, presence: true + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/poll/officer.rb b/app/models/poll/officer.rb new file mode 100644 index 000000000..bf4c73c36 --- /dev/null +++ b/app/models/poll/officer.rb @@ -0,0 +1,26 @@ +class Poll + class Officer < ActiveRecord::Base + belongs_to :user + has_many :officer_assignments, class_name: "Poll::OfficerAssignment" + has_many :failed_census_calls, foreign_key: :poll_officer_id + + validates :user_id, presence: true, uniqueness: true + + delegate :name, :email, to: :user + + def voting_days_assigned_polls + officer_assignments.voting_days.includes(booth_assignment: :poll). + map(&:booth_assignment). + map(&:poll).uniq.compact. + sort {|x, y| y.ends_at <=> x.ends_at} + end + + def final_days_assigned_polls + officer_assignments.final.includes(booth_assignment: :poll). + map(&:booth_assignment). + map(&:poll).uniq.compact. + sort {|x, y| y.ends_at <=> x.ends_at} + end + + end +end diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb new file mode 100644 index 000000000..cd4f53266 --- /dev/null +++ b/app/models/poll/officer_assignment.rb @@ -0,0 +1,25 @@ +class Poll + class OfficerAssignment < ActiveRecord::Base + belongs_to :officer + belongs_to :booth_assignment + has_one :recount + has_many :final_recounts + has_many :partial_results + has_many :voters + + validates :officer_id, presence: true + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: { scope: [:officer_id, :booth_assignment_id] } + + delegate :poll_id, :booth_id, to: :booth_assignment + + scope :voting_days, -> { where(final: false) } + scope :final, -> { where(final: true) } + + before_create :log_user_data + + def log_user_data + self.user_data_log = "#{officer.user_id} - #{officer.user.name_and_email}" + end + end +end diff --git a/app/models/poll/partial_result.rb b/app/models/poll/partial_result.rb new file mode 100644 index 000000000..e42589a03 --- /dev/null +++ b/app/models/poll/partial_result.rb @@ -0,0 +1,28 @@ +class Poll::PartialResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :question, presence: true + validates :author, presence: true + validates :answer, presence: true + validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + scope :by_question, -> (question_id) { where(question_id: question_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb new file mode 100644 index 000000000..dd4eae3bc --- /dev/null +++ b/app/models/poll/question.rb @@ -0,0 +1,70 @@ +class Poll::Question < ActiveRecord::Base + include Measurable + include Searchable + + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :poll + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + + has_many :comments, as: :commentable + has_many :answers + has_many :partial_results + belongs_to :proposal + + validates :title, presence: true + validates :author, presence: true + + validates :title, length: { minimum: 4 } + validates :description, length: { maximum: Poll::Question.description_max_length } + + scope :by_poll_id, ->(poll_id) { where(poll_id: poll_id) } + + scope :sort_for_list, -> { order('poll_questions.proposal_id IS NULL', :created_at)} + scope :for_render, -> { includes(:author, :proposal) } + + def self.search(params) + results = self.all + results = results.by_poll_id(params[:poll_id]) if params[:poll_id].present? + results = results.pg_search(params[:search]) if params[:search].present? + results + end + + def searchable_values + { title => 'A', + proposal.try(:title) => 'A', + description => 'B', + author.username => 'C', + author_visible_name => 'C' } + end + + def description + super.try :html_safe + end + + def valid_answers + (super.try(:split, ',').compact || []).map(&:strip) + end + + def copy_attributes_from_proposal(proposal) + if proposal.present? + self.author = proposal.author + self.author_visible_name = proposal.author.name + self.proposal_id = proposal.id + self.title = proposal.title + self.description = proposal.description + self.valid_answers = I18n.t('poll_questions.default_valid_answers') + end + end + + def answerable_by?(user) + poll.answerable_by?(user) + end + + def self.answerable_by(user) + return none if user.nil? || user.unverified? + where(poll_id: Poll.answerable_by(user).pluck(:id)) + end + +end diff --git a/app/models/poll/recount.rb b/app/models/poll/recount.rb new file mode 100644 index 000000000..b4e28583e --- /dev/null +++ b/app/models/poll/recount.rb @@ -0,0 +1,20 @@ +class Poll + class Recount < ActiveRecord::Base + belongs_to :booth_assignment, class_name: "Poll::BoothAssignment" + belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" + + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :officer_assignment_id, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :count, presence: true, numericality: {only_integer: true} + + before_save :update_logs + + def update_logs + if self.count_changed? && self.count_was.present? + self.count_log += ":#{self.count_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + end + end + end +end \ No newline at end of file diff --git a/app/models/poll/voter.rb b/app/models/poll/voter.rb new file mode 100644 index 000000000..8fe612151 --- /dev/null +++ b/app/models/poll/voter.rb @@ -0,0 +1,59 @@ +class Poll + class Voter < ActiveRecord::Base + belongs_to :poll + belongs_to :user + belongs_to :geozone + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :poll_id, presence: true + validates :user_id, presence: true + + validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted } + + before_validation :set_demographic_info, :set_document_info + + def set_demographic_info + return unless user.present? + + self.gender = user.gender + self.age = user.age + self.geozone = user.geozone + end + + def set_document_info + return unless user.present? + + self.document_type = user.document_type + self.document_number = user.document_number + end + + private + + def in_census? + census_api_response.valid? + end + + def census_api_response + @census_api_response ||= CensusApi.new.call(document_type, document_number) + end + + def fill_stats_fields + if in_census? + self.gender = census_api_response.gender + self.geozone_id = Geozone.select(:id).where(census_code: census_api_response.district_code).first.try(:id) + self.age = voter_age(census_api_response.date_of_birth) + end + end + + def voter_age(dob) + if dob.blank? + nil + else + now = Time.now.utc.to_date + now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1) + end + end + + end +end \ No newline at end of file diff --git a/app/models/poll/white_result.rb b/app/models/poll/white_result.rb new file mode 100644 index 000000000..5b0aa4966 --- /dev/null +++ b/app/models/poll/white_result.rb @@ -0,0 +1,23 @@ +class Poll::WhiteResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :author, presence: true + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 32175f9f7..0abde3584 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -45,12 +45,12 @@ class Proposal < ActiveRecord::Base scope :sort_by_relevance, -> { all } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } scope :sort_by_archival_date, -> { archived.sort_by_confidence_score } - scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago)} - scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago)} + scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago) } + scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago) } scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)} scope :retired, -> { where.not(retired_at: nil) } scope :not_retired, -> { where(retired_at: nil) } - scope :successfull, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success)} + scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) } def to_param "#{id}-#{title}".parameterize @@ -155,7 +155,7 @@ class Proposal < ActiveRecord::Base Setting['votes_for_proposal_success'].to_i end - def successfull? + def successful? total_votes >= Proposal.votes_needed_for_success end diff --git a/app/models/signature.rb b/app/models/signature.rb index 06ade968c..543965aed 100644 --- a/app/models/signature.rb +++ b/app/models/signature.rb @@ -12,35 +12,30 @@ class Signature < ActiveRecord::Base before_validation :clean_document_number - def verified? - user_exists? || in_census? - end - def verify - if verified? - assign_vote - mark_as_verified - end - end - - def assign_vote if user_exists? assign_vote_to_user - else + mark_as_verified + elsif in_census? create_user assign_vote_to_user + mark_as_verified end end def assign_vote_to_user set_user - signable.register_vote(user, "yes") + if signable.is_a? Budget::Investment + signable.vote_by(voter: user, vote: 'yes') if [nil, :no_selecting_allowed].include?(signable.reason_for_not_being_selectable_by(user)) + else + signable.register_vote(user, "yes") + end assign_signature_to_vote end def assign_signature_to_vote vote = Vote.where(votable: signable, voter: user).first - vote.update(signature: self) + vote.update(signature: self) if vote end def user_exists? @@ -51,11 +46,14 @@ class Signature < ActiveRecord::Base user_params = { document_number: document_number, created_from_signature: true, - verified_at: Time.now, - erased_at: Time.now, + verified_at: Time.current, + erased_at: Time.current, password: random_password, terms_of_service: '1', - email: nil + email: nil, + date_of_birth: @census_api_response.date_of_birth, + gender: @census_api_response.gender, + geozone: Geozone.where(census_code: @census_api_response.district_code).first } User.create!(user_params) end @@ -70,10 +68,17 @@ class Signature < ActiveRecord::Base end def in_census? - response = document_types.detect do |document_type| - CensusApi.new.call(document_type, document_number).valid? + document_types.detect do |document_type| + response = CensusApi.new.call(document_type, document_number) + if response.valid? + @census_api_response = response + true + else + false + end end - response.present? + + @census_api_response.present? end def set_user diff --git a/app/models/signature_sheet.rb b/app/models/signature_sheet.rb index 9720c891f..1434143ac 100644 --- a/app/models/signature_sheet.rb +++ b/app/models/signature_sheet.rb @@ -2,7 +2,7 @@ class SignatureSheet < ActiveRecord::Base belongs_to :signable, polymorphic: true belongs_to :author, class_name: 'User', foreign_key: 'author_id' - VALID_SIGNABLES = %w( Proposal SpendingProposal ) + VALID_SIGNABLES = %w( Proposal Budget::Investment SpendingProposal ) has_many :signatures diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb new file mode 100644 index 000000000..e5d2f2137 --- /dev/null +++ b/app/models/site_customization.rb @@ -0,0 +1,5 @@ +module SiteCustomization + def self.table_name_prefix + 'site_customization_' + end +end diff --git a/app/models/site_customization/content_block.rb b/app/models/site_customization/content_block.rb new file mode 100644 index 000000000..c08beb52e --- /dev/null +++ b/app/models/site_customization/content_block.rb @@ -0,0 +1,11 @@ +class SiteCustomization::ContentBlock < ActiveRecord::Base + VALID_BLOCKS = %w(top_links footer) + + validates :locale, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) } + validates :name, presence: true, uniqueness: { scope: :locale }, inclusion: { in: VALID_BLOCKS } + + def self.block_for(name, locale) + locale ||= I18n.default_locale + find_by(name: name, locale: locale).try(:body) + end +end diff --git a/app/models/site_customization/image.rb b/app/models/site_customization/image.rb new file mode 100644 index 000000000..2230a96ce --- /dev/null +++ b/app/models/site_customization/image.rb @@ -0,0 +1,48 @@ +class SiteCustomization::Image < ActiveRecord::Base + VALID_IMAGES = { + "icon_home" => [330, 240], + "logo_header" => [80, 80], + "social-media-icon" => [200, 200], + "apple-touch-icon-200" => [200, 200] + } + + has_attached_file :image + + validates :name, presence: true, uniqueness: true, inclusion: { in: VALID_IMAGES.keys } + validates_attachment_content_type :image, :content_type => ["image/png"] + validate :check_image + + def self.all_images + VALID_IMAGES.keys.map do |image_name| + find_by(name: image_name) || create!(name: image_name.to_s) + end + end + + def self.image_path_for(filename) + image_name = filename.split(".").first + + if i = find_by(name: image_name) + i.image.exists? ? i.image.url : nil + end + end + + def required_width + VALID_IMAGES[name].try(:first) + end + + def required_height + VALID_IMAGES[name].try(:second) + end + + private + + def check_image + return unless image? + + dimensions = Paperclip::Geometry.from_file(image.queued_for_write[:original].path) + + errors.add(:image, :image_width, required_width: required_width) unless dimensions.width == required_width + errors.add(:image, :image_height, required_height: required_height) unless dimensions.height == required_height + end + +end diff --git a/app/models/site_customization/page.rb b/app/models/site_customization/page.rb new file mode 100644 index 000000000..c2a9b1467 --- /dev/null +++ b/app/models/site_customization/page.rb @@ -0,0 +1,16 @@ +class SiteCustomization::Page < ActiveRecord::Base + VALID_STATUSES = %w(draft published) + + validates :slug, presence: true, + uniqueness: { case_sensitive: false }, + format: { with: /\A[0-9a-zA-Z\-_]*\Z/, message: :slug_format } + validates :title, presence: true + validates :status, presence: true, inclusion: { in: VALID_STATUSES } + + scope :published, -> { where(status: 'published').order('id DESC') } + scope :with_more_info_flag, -> { where(status: 'published', more_info_flag: true).order('id ASC') } + + def url + "/#{slug}" + end +end diff --git a/app/models/user.rb b/app/models/user.rb index f8991d676..c3038c88a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,6 +13,7 @@ class User < ActiveRecord::Base has_one :moderator has_one :valuator has_one :manager + has_one :poll_officer, class_name: "Poll::Officer" has_one :organization has_one :lock has_many :flags @@ -55,6 +56,7 @@ class User < ActiveRecord::Base scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) } scope :email_digest, -> { where(email_digest: true) } scope :active, -> { where(erased_at: nil) } + scope :erased, -> { where.not(erased_at: nil) } before_validation :clean_document_number @@ -123,6 +125,10 @@ class User < ActiveRecord::Base manager.present? end + def poll_officer? + poll_officer.present? + end + def organization? organization.present? end @@ -188,6 +194,22 @@ class User < ActiveRecord::Base erased_at.present? end + def take_votes_if_erased_document(document_number, document_type) + erased_user = User.erased.where(document_number: document_number).where(document_type: document_type).first + if erased_user.present? + self.take_votes_from(erased_user) + erased_user.update(document_number: nil, document_type: nil) + end + end + + def take_votes_from(other_user) + return if other_user.blank? + Poll::Voter.where(user_id: other_user.id).update_all(user_id: self.id) + Budget::Ballot.where(user_id: other_user.id).update_all(user_id: self.id) + Vote.where("voter_id = ? AND voter_type = ?", other_user.id, "User").update_all(voter_id: self.id) + self.update(former_users_data_log: "#{self.former_users_data_log} | id: #{other_user.id} - #{Time.current.strftime('%Y-%m-%d %H:%M:%S')}") + end + def locked? Lock.find_or_create_by(user: self).locked? end @@ -241,6 +263,10 @@ class User < ActiveRecord::Base "#{name} (#{email})" end + def age + Age.in_years(date_of_birth) + end + def save_requiring_finish_signup begin self.registering_with_oauth = true diff --git a/app/models/verification/management/document.rb b/app/models/verification/management/document.rb index 4264154d3..420dcf49c 100644 --- a/app/models/verification/management/document.rb +++ b/app/models/verification/management/document.rb @@ -32,7 +32,7 @@ class Verification::Management::Document end def under_age?(response) - User.minimum_required_age.years.ago.beginning_of_day < response.date_of_birth.beginning_of_day + response.date_of_birth.blank? || Age.in_years(response.date_of_birth) < User.minimum_required_age end def verified? diff --git a/app/models/verification/residence.rb b/app/models/verification/residence.rb index eda562671..ea000677f 100644 --- a/app/models/verification/residence.rb +++ b/app/models/verification/residence.rb @@ -26,6 +26,9 @@ class Verification::Residence def save return false unless valid? + + user.take_votes_if_erased_document(document_number, document_type) + user.update(document_number: document_number, document_type: document_type, geozone: self.geozone, @@ -36,11 +39,11 @@ class Verification::Residence def allowed_age return if errors[:date_of_birth].any? - errors.add(:date_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) unless self.date_of_birth <= User.minimum_required_age.years.ago + errors.add(:date_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) unless Age.in_years(self.date_of_birth) >= User.minimum_required_age end def document_number_uniqueness - errors.add(:document_number, I18n.t('errors.messages.taken')) if User.where(document_number: document_number).any? + errors.add(:document_number, I18n.t('errors.messages.taken')) if User.active.where(document_number: document_number).any? end def store_failed_attempt diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index d603dbf16..fecd29419 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -1,126 +1,166 @@ - + diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index 793b4e407..2a2ed1c56 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -1,4 +1,12 @@ -<%= link_to t("admin.dashboard.index.back") + " " + setting['org_name'], root_path, class: "button float-right" %> +<%= link_to admin_settings_path, class: "button float-right" do %> + + <%= t("admin.menu.settings") %> +<% end %> + +<%= link_to admin_stats_path, class: "button float-right" do %> + + <%= t("admin.menu.stats") %> +<% end %>

<%= t("admin.dashboard.index.title") %>

diff --git a/app/views/admin/moderators/index.html.erb b/app/views/admin/moderators/index.html.erb index d2a5d45e0..0fc8703fd 100644 --- a/app/views/admin/moderators/index.html.erb +++ b/app/views/admin/moderators/index.html.erb @@ -1,4 +1,6 @@ -

<%= t("admin.moderators.index.title") %>

+<%= link_to t('admin.menu.activity'), admin_activity_path, class: "button hollow float-right" %> + +

<%= t("admin.moderators.index.title") %>

<%= form_tag search_admin_moderators_path, method: :get, remote: true do %> diff --git a/app/views/admin/poll/_menu.html.erb b/app/views/admin/poll/_menu.html.erb new file mode 100644 index 000000000..08efd87dc --- /dev/null +++ b/app/views/admin/poll/_menu.html.erb @@ -0,0 +1 @@ +<%= render "admin/menu" %> \ No newline at end of file diff --git a/app/views/admin/poll/booth_assignments/_search_booths.html.erb b/app/views/admin/poll/booth_assignments/_search_booths.html.erb new file mode 100644 index 000000000..e96e334c7 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/_search_booths.html.erb @@ -0,0 +1,16 @@ +
+
+ <%= form_tag(search_booths_admin_poll_booth_assignments_path(@poll), method: :get, remote: true) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.booths_search.placeholder"), id: "search-booths" %> +
+ <%= submit_tag t("admin.shared.booths_search.button"), class: "button" %> +
+
+ <% end %> +
+
+ +
diff --git a/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb b/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb new file mode 100644 index 000000000..3fa7fc080 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb @@ -0,0 +1,39 @@ +<% if @booths.blank? %> +
+ <%= t('admin.shared.no_search_results') %> +
+<% else %> +

<%= t('admin.shared.search_results') %>

+<% end %> + +<% if @booths.any? %> + + + + + + + + + + <% @booths.each do |booth| %> + + + + + + <% end %> + +
<%= t("admin.poll_booth_assignments.index.table_name") %><%= t("admin.poll_booth_assignments.index.table_location") %><%= t("admin.poll_booth_assignments.index.table_assignment") %>
+ <%= booth.name %> + + <%= booth.location %> + + <% unless @poll.booth_ids.include?(booth.id) %> + <%= link_to t("admin.poll_booth_assignments.index.add_booth"), + admin_poll_booth_assignments_path(@poll, booth_id: booth.id), + method: :post, + class: "button hollow" %> + <% end %> +
+<% end %> diff --git a/app/views/admin/poll/booth_assignments/index.html.erb b/app/views/admin/poll/booth_assignments/index.html.erb new file mode 100644 index 000000000..f14e59ef1 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/index.html.erb @@ -0,0 +1,43 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + <%= render "search_booths" %> + +

<%= t("admin.poll_booth_assignments.index.booths_title") %>

+ + <% if @booth_assignments.empty? %> +
+ <%= t("admin.poll_booth_assignments.index.no_booths") %> +
+ <% else %> + + + + + + + + <% @booth_assignments.each do |booth_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_booth_assignments.index.table_name") %><%= t("admin.poll_booth_assignments.index.table_location") %><%= t("admin.poll_booth_assignments.index.table_assignment") %>
+ + <%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment) %> + + + <%= booth_assignment.booth.location %> + + <%= link_to t("admin.poll_booth_assignments.index.remove_booth"), + admin_poll_booth_assignment_path(@poll, booth_assignment), + method: :delete, + class: "button hollow alert" %> +
+ + <%= paginate @booth_assignments %> + <% end %> +
diff --git a/app/views/admin/poll/booth_assignments/search_booths.js.erb b/app/views/admin/poll/booth_assignments/search_booths.js.erb new file mode 100644 index 000000000..72fd96f68 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/search_booths.js.erb @@ -0,0 +1 @@ +$("#search-booths-results").html("<%= j render 'search_booths_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/booth_assignments/show.html.erb b/app/views/admin/poll/booth_assignments/show.html.erb new file mode 100644 index 000000000..519b36202 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/show.html.erb @@ -0,0 +1,87 @@ +<%= link_to admin_poll_booth_assignments_path(@poll) do %> + + <%= @poll.name %> +<% end %> + +

<%= @booth_assignment.booth.name %>

+ +<% if @booth_assignment.booth.location.present? %> +

+ <%= t("admin.poll_booth_assignments.show.location") %>: + <%= @booth_assignment.booth.location %> +

+<% end %> + +
+
    +
  • + <%= link_to t("admin.poll_booth_assignments.show.officers"), "#tab-officers" %> +
  • +
  • + <%= link_to t("admin.poll_booth_assignments.show.recounts"), "#tab-recounts" %> +
  • +
+ +
+ <% if @booth_assignment.officers.empty? %> +
+ <%= t("admin.poll_booth_assignments.show.no_officers") %> +
+ <% else %> +

<%= t("admin.poll_booth_assignments.show.officers_list") %>

+ + + + <% @booth_assignment.officers.uniq.each do |officer| %> + + + + + <% end %> + +
<%= link_to officer.name, by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id) %><%= officer.email %>
+ <% end %> +
+ +
+ <% if @booth_assignment.recounts.empty? %> +
+ <%= t("admin.poll_booth_assignments.show.no_recounts") %> +
+ <% else %> +

<%= t("admin.poll_booth_assignments.show.recounts_list") %>

+ + + + + + + + + + + <% (@poll.starts_at.to_date..@poll.ends_at.to_date).each do |voting_date| %> + <% recount = recount_for_date(@booth_assignment.recounts, voting_date) %> + <% final_recount = final_recount_for_date(@booth_assignment.final_recounts, voting_date) %> + <% system_count = @voters_by_date[voting_date].present? ? @voters_by_date[voting_date].size : 0 %> + + + <% if recount.present? %> + + + <% else %> + + <% end %> + <% if final_recount.present? %> + + <% else %> + + <% end %> + + + <% end %> + +
<%= t("admin.poll_booth_assignments.show.date") %><%= t("admin.poll_booth_assignments.show.count_by_officer") %><%= t("admin.poll_booth_assignments.show.count_final") %><%= t("admin.poll_booth_assignments.show.count_by_system") %>
<%= l voting_date %><%= recount.count %> - <%= final_recount.count %> - <%= system_count %>
+ <% end %> +
+
diff --git a/app/views/admin/poll/booths/_booth.html.erb b/app/views/admin/poll/booths/_booth.html.erb new file mode 100644 index 000000000..5732400a8 --- /dev/null +++ b/app/views/admin/poll/booths/_booth.html.erb @@ -0,0 +1,13 @@ + + + <%= booth.name %> + + + <%= booth.location %> + + + <%= link_to t("admin.actions.edit"), + edit_admin_booth_path(booth), + class: "button hollow" %> + + \ No newline at end of file diff --git a/app/views/admin/poll/booths/_form.html.erb b/app/views/admin/poll/booths/_form.html.erb new file mode 100644 index 000000000..31d60c0f0 --- /dev/null +++ b/app/views/admin/poll/booths/_form.html.erb @@ -0,0 +1,20 @@ +
+
+ <%= f.text_field :name, + placeholder: t('admin.booths.new.name'), + label: t("admin.booths.new.name") %> +
+ +
+ <%= f.text_field :location, + placeholder: t("admin.booths.new.location"), + label: t("admin.booths.new.location") %> +
+
+ +
+
+ <%= f.submit t("admin.booths.#{admin_submit_action(@booth)}.submit_button"), + class: "button success expanded" %> +
+
\ No newline at end of file diff --git a/app/views/admin/poll/booths/edit.html.erb b/app/views/admin/poll/booths/edit.html.erb new file mode 100644 index 000000000..66bfa8355 --- /dev/null +++ b/app/views/admin/poll/booths/edit.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_booths_path %> + +

<%= t("admin.booths.edit.title") %>

+ +<%= form_for @booth, url: admin_booth_path(@booth) do |f| %> + <%= render "form", f: f %> +<% end %> diff --git a/app/views/admin/poll/booths/index.html.erb b/app/views/admin/poll/booths/index.html.erb new file mode 100644 index 000000000..9618aec59 --- /dev/null +++ b/app/views/admin/poll/booths/index.html.erb @@ -0,0 +1,28 @@ +

<%= t("admin.booths.index.title") %>

+ +<%= link_to t("admin.booths.index.add_booth"), new_admin_booth_path, + class: "button success float-right" %> + +<% if @booths.empty? %> +
+ <%= t("admin.booths.index.no_booths") %> +
+<% end %> + +<% if @booths.any? %> +

<%= page_entries_info @booths %>

+ + + + + + + + <% @booths.each do |booth| %> + <%= render partial: "booth", locals: { booth: booth } %> + <% end %> + +
<%= t("admin.booths.index.name") %><%= t("admin.booths.index.location") %> 
+ + <%= paginate @booths %> +<% end %> diff --git a/app/views/admin/poll/booths/new.html.erb b/app/views/admin/poll/booths/new.html.erb new file mode 100644 index 000000000..cf9581fa3 --- /dev/null +++ b/app/views/admin/poll/booths/new.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_booths_path %> + +

<%= t("admin.booths.new.title") %>

+ +<%= form_for @booth, url: admin_booths_path(@booth) do |f| %> + <%= render "form", f: f %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/booths/show.html.erb b/app/views/admin/poll/booths/show.html.erb new file mode 100644 index 000000000..bd9355f69 --- /dev/null +++ b/app/views/admin/poll/booths/show.html.erb @@ -0,0 +1,12 @@ +<%= back_link_to admin_booths_path %> + +
+ +

+ <%= @booth.name %> +

+ +

+ <%= t("admin.booths.show.location") %>: + <%= @booth.location %> +

diff --git a/app/views/admin/poll/officer_assignments/_search_officers.html.erb b/app/views/admin/poll/officer_assignments/_search_officers.html.erb new file mode 100644 index 000000000..7ed39f10d --- /dev/null +++ b/app/views/admin/poll/officer_assignments/_search_officers.html.erb @@ -0,0 +1,16 @@ +
+
+ <%= form_tag(search_officers_admin_poll_officer_assignments_path(@poll), method: :get, remote: true) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_officers_search.placeholder"), id: "search-officers" %> +
+ <%= submit_tag t("admin.shared.poll_officers_search.button"), class: "button" %> +
+
+ <% end %> +
+
+ +
diff --git a/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb b/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb new file mode 100644 index 000000000..f665c6c0b --- /dev/null +++ b/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb @@ -0,0 +1,42 @@ +<% if @officers.blank? %> +
+ <%= t('admin.shared.no_search_results') %> +
+<% else %> +

<%= t('admin.shared.search_results') %>

+<% end %> + +<% if @officers.any? %> + + + + + + + + + + <% @officers.each do |user| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.index.table_name") %><%= t("admin.poll_officer_assignments.index.table_email") %><%= t("admin.polls.show.table_assignment") %>
+ <%= user.name %> + + <%= user.email %> + + <% if @poll.officer_ids.include?(user.poll_officer.id) %> + <%= link_to t("admin.poll_officer_assignments.index.edit_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: user.poll_officer.id), + class: "button hollow alert" %> + <% else %> + <%= link_to t("admin.poll_officer_assignments.index.add_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: user.poll_officer.id), + class: "button hollow" %> + <% end %> +
+<% end %> diff --git a/app/views/admin/poll/officer_assignments/by_officer.html.erb b/app/views/admin/poll/officer_assignments/by_officer.html.erb new file mode 100644 index 000000000..205692535 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/by_officer.html.erb @@ -0,0 +1,127 @@ +<%= link_to admin_poll_officer_assignments_path(@poll) do %> + + <%= @poll.name %> +<% end %> + +

<%= @officer.name %> - <%= @officer.email %>

+ +<%= form_tag(admin_poll_officer_assignments_path(@poll), {id: "officer_assignment_form"}) do %> +
+ <%= t("admin.poll_officer_assignments.by_officer.new_assignment") %> +
+ + <%= select_tag :date, + poll_dates_select_options(@poll) + poll_final_recount_option(@poll), + { prompt: t("admin.poll_officer_assignments.by_officer.select_date"), + label: false } %> +
+ +
+ + <%= select_tag :booth_id, + poll_booths_select_options(@poll), + { prompt: t("admin.poll_officer_assignments.by_officer.select_booth"), + label: false } %> +
+ +
+ <%= hidden_field_tag :officer_id, @officer.id %> + <%= hidden_field_tag :poll_id, @poll.id %> + <%= submit_tag t("admin.poll_officer_assignments.by_officer.add_assignment"), + class: "button expanded hollow margin-top" %> +
+
+<% end %> + + +<% if @officer_assignments.empty? %> +
+ <%= t("admin.poll_officer_assignments.by_officer.no_assignments") %> +
+<% else %> +

<%= t("admin.poll_officer_assignments.by_officer.assignments") %>

+ + + + + + + + + + <% @officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.assignment") %>
<%= officer_assignment.final? ? t('polls.final_date') : l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <%= link_to t("admin.poll_officer_assignments.by_officer.remove_assignment"), + admin_poll_officer_assignment_path(@poll, officer_assignment), + method: :delete, + class: "button hollow alert" %> +
+<% end %> + +<% voting_days_officer_assignments = @officer_assignments.select{|oa| oa.final == false} %> +<% if voting_days_officer_assignments.any? %> +

<%= t("admin.poll_officer_assignments.by_officer.recounts") %>

+ + + + + + + + + + <% voting_days_officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.recount") %>
<%= l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <% if officer_assignment.recount.present? %> + <%= officer_assignment.recount.count %> + <% else %> + - + <% end %> +
+<% end %> + +<% final_officer_assignments = @officer_assignments.select{|oa| oa.final == true} %> +<% if final_officer_assignments.any? %> +

<%= t("admin.poll_officer_assignments.by_officer.final_recounts") %>

+ + + + + + + + + + <% final_officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.final_recount") %>
<%= l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <% if officer_assignment.final_recounts.any? %> + <%= officer_assignment.final_recounts.to_a.sum(&:count) %> + <% else %> + - + <% end %> +
+<% end %> + + + diff --git a/app/views/admin/poll/officer_assignments/index.html.erb b/app/views/admin/poll/officer_assignments/index.html.erb new file mode 100644 index 000000000..f6e75c602 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/index.html.erb @@ -0,0 +1,43 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + + <%= render "search_officers" %> + +

<%= t("admin.poll_officer_assignments.index.officers_title") %>

+ + <% if @officers.empty? %> +
+ <%= t("admin.poll_officer_assignments.index.no_officers") %> +
+ <% else %> + + + + + + + + <% @officers.each do |officer| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.index.table_name") %><%= t("admin.poll_officer_assignments.index.table_email") %><%= t("admin.actions.actions") %>
+ + <%= link_to officer.name, by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id) %> + + + <%= officer.email %> + + <%= link_to t("admin.poll_officer_assignments.index.edit_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id), + class: "button hollow" %> +
+ + <%= paginate @officers %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/poll/officer_assignments/search_officers.js.erb b/app/views/admin/poll/officer_assignments/search_officers.js.erb new file mode 100644 index 000000000..ba621d8f7 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/search_officers.js.erb @@ -0,0 +1 @@ +$("#search-officers-results").html("<%= j render 'search_officers_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/officers/_officer.html.erb b/app/views/admin/poll/officers/_officer.html.erb new file mode 100644 index 000000000..80434f385 --- /dev/null +++ b/app/views/admin/poll/officers/_officer.html.erb @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + +
<%= t('admin.poll_officers.officer.name') %><%= t('admin.poll_officers.officer.email') %>
+ <%= officer.name %> + + <%= officer.email %> + + <% if officer.persisted? %> + <%= link_to t('admin.poll_officers.officer.delete'), + admin_poll_officer_path(officer), + method: :delete, + class: "button hollow alert" %> + <% else %> + <%= link_to t('admin.poll_officers.officer.add'),{ controller: "admin/poll/officers", action: :create, user_id: officer.user_id }, + method: :post, + class: "button success" %> + <% end %> +
diff --git a/app/views/admin/poll/officers/_search.html.erb b/app/views/admin/poll/officers/_search.html.erb new file mode 100644 index 000000000..dda22dee2 --- /dev/null +++ b/app/views/admin/poll/officers/_search.html.erb @@ -0,0 +1,9 @@ +<%= form_tag search_admin_officers_path, method: :get, remote: true do %> +
+ <%= text_field_tag :email, '', + placeholder: t("admin.poll_officers.search.email_placeholder") %> +
+ <%= submit_tag t("admin.poll_officers.search.search"), class: "button" %> +
+
+<% end %> diff --git a/app/views/admin/poll/officers/edit.html.erb b/app/views/admin/poll/officers/edit.html.erb new file mode 100644 index 000000000..5c64d3aeb --- /dev/null +++ b/app/views/admin/poll/officers/edit.html.erb @@ -0,0 +1 @@ +officer edit \ No newline at end of file diff --git a/app/views/admin/poll/officers/index.html.erb b/app/views/admin/poll/officers/index.html.erb new file mode 100644 index 000000000..fd9619167 --- /dev/null +++ b/app/views/admin/poll/officers/index.html.erb @@ -0,0 +1,53 @@ +

<%= t("admin.poll_officers.index.title") %>

+ +
+
+ <%= render 'search' %> +
+
+ +
+ +

+ <%= page_entries_info @officers, entry_name: t('admin.poll_officers.officer.entry_name') %> +

+ +<% if @officers.any? %> + + + + + + + + + <% @officers.each do |officer| %> + + + + + + <% end %> + +
<%= t('admin.poll_officers.officer.name') %><%= t('admin.poll_officers.officer.email') %>
+ <%= officer.name %> + + <%= officer.email %> + + <% if officer.persisted? %> + <%= link_to t('admin.poll_officers.officer.delete'), + admin_officer_path(officer), + method: :delete, + class: "button hollow alert" + %> + <% else %> + <%= link_to t('admin.poll_officers.officer.add'), + { controller: "admin/poll/officers", action: :create, + user_id: officer.user_id }, + method: :post, + class: "button success" %> + <% end %> +
+ + <%= paginate @officers %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/officers/search.js.erb b/app/views/admin/poll/officers/search.js.erb new file mode 100644 index 000000000..bd259f7fb --- /dev/null +++ b/app/views/admin/poll/officers/search.js.erb @@ -0,0 +1 @@ +$("#search-result").html("<%= j render 'officer', officer: @officer %>"); diff --git a/app/views/admin/poll/officers/show.html.erb b/app/views/admin/poll/officers/show.html.erb new file mode 100644 index 000000000..fc702276e --- /dev/null +++ b/app/views/admin/poll/officers/show.html.erb @@ -0,0 +1 @@ +officer show \ No newline at end of file diff --git a/app/views/admin/poll/officers/user_not_found.js.erb b/app/views/admin/poll/officers/user_not_found.js.erb new file mode 100644 index 000000000..a6444dc61 --- /dev/null +++ b/app/views/admin/poll/officers/user_not_found.js.erb @@ -0,0 +1 @@ +$("#search-result").html("
<%= j t('admin.poll_officers.search.user_not_found') %>
"); diff --git a/app/views/admin/poll/polls/_form.html.erb b/app/views/admin/poll/polls/_form.html.erb new file mode 100644 index 000000000..0b99ea9f5 --- /dev/null +++ b/app/views/admin/poll/polls/_form.html.erb @@ -0,0 +1,46 @@ +<%= form_for [:admin, @poll] do |f| %> +
+
+ <%= f.text_field :name %> +
+
+ +
+
+ <%= f.text_field :starts_at, + value: @poll.starts_at.present? ? l(@poll.starts_at.to_date) : nil, + class: "js-calendar-full" %> +
+ +
+ <%= f.text_field :ends_at, + value: @poll.ends_at.present? ? l(@poll.ends_at.to_date) : nil, + class: "js-calendar-full" %> +
+
+ +
+
+ <%= f.check_box :geozone_restricted, data: { checkbox_toggle: "#geozones" } %> +
+
+ +
+
+ <%= f.collection_check_boxes(:geozone_ids, @geozones, :id, :name) do |b| %> +
+ <%= b.label do %> + <%= b.check_box + b.text %> + <% end %> +
+ <% end %> +
+
+ +
+
+ <%= f.submit t("admin.polls.#{admin_submit_action(@poll)}.submit_button"), + class: "button success expanded" %> +
+
+<% end %> diff --git a/app/views/admin/poll/polls/_poll.html.erb b/app/views/admin/poll/polls/_poll.html.erb new file mode 100644 index 000000000..b65ce3e71 --- /dev/null +++ b/app/views/admin/poll/polls/_poll.html.erb @@ -0,0 +1,18 @@ + + + + <%= link_to poll.name, admin_poll_path(poll) %> + + + + <%= l poll.starts_at.to_date %> - <%= l poll.ends_at.to_date %> + + + <%= link_to t("admin.actions.edit"), + edit_admin_poll_path(poll), + class: "button hollow" %> + <%= link_to t("admin.actions.configure"), + admin_poll_path(poll), + class: "button hollow" %> + + \ No newline at end of file diff --git a/app/views/admin/poll/polls/_poll_header.html.erb b/app/views/admin/poll/polls/_poll_header.html.erb new file mode 100644 index 000000000..a88dd03ee --- /dev/null +++ b/app/views/admin/poll/polls/_poll_header.html.erb @@ -0,0 +1,17 @@ +<%= link_to t("admin.actions.edit"), + edit_admin_poll_path(@poll), + class: "button hollow float-right" %> + +

+ <%= @poll.name %> +

+
+ + (<%= l @poll.starts_at.to_date %> - <%= l @poll.ends_at.to_date %>) + +<% if @poll.geozone_restricted %> +  •  + + <%= @poll.geozones.pluck(:name).to_sentence %> + +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/_questions.html.erb b/app/views/admin/poll/polls/_questions.html.erb new file mode 100644 index 000000000..e41d2017e --- /dev/null +++ b/app/views/admin/poll/polls/_questions.html.erb @@ -0,0 +1,31 @@ +

<%= t("admin.polls.show.questions_title") %>

+ +<% if @poll.questions.empty? %> +
+ <%= t('admin.polls.show.no_questions') %> +
+<% else %> + + + + + + + + <% @poll.questions.each do |question| %> + + + + + <% end %> +
<%= t('admin.polls.show.table_title') %><%= t('admin.polls.show.table_assignment') %>
+ + <%= link_to question.title, admin_question_path(question) %> + + + <%= link_to t('admin.polls.show.remove_question'), + remove_question_admin_poll_path(poll_id: @poll.id, question_id: question.id), + class: "button hollow alert", + method: :patch %> +
+<% end %> diff --git a/app/views/admin/poll/polls/_search_questions.html.erb b/app/views/admin/poll/polls/_search_questions.html.erb new file mode 100644 index 000000000..659cdcd37 --- /dev/null +++ b/app/views/admin/poll/polls/_search_questions.html.erb @@ -0,0 +1,17 @@ +
+
+ <%= form_tag(search_questions_admin_poll_path(@poll), method: :get, remote: true) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_questions_search.placeholder"), id: "search-questions" %> + +
+ <%= submit_tag t("admin.shared.poll_questions_search.button"), class: "button" %> +
+
+ <% end %> +
+
+ +
diff --git a/app/views/admin/poll/polls/_search_questions_results.html.erb b/app/views/admin/poll/polls/_search_questions_results.html.erb new file mode 100644 index 000000000..f6f11e1f1 --- /dev/null +++ b/app/views/admin/poll/polls/_search_questions_results.html.erb @@ -0,0 +1,33 @@ +<% if @questions.blank? %> +
+ <%= t('admin.shared.no_search_results') %> +
+<% else %> +

<%= t('admin.shared.search_results') %>

+<% end %> + +<% if @questions.any? %> + + + + + + + + + <% @questions.each do |question| %> + + + + + <% end %> + +
<%= t("admin.polls.show.table_name") %><%= t("admin.polls.show.table_assignment") %>
+ <%= question.title %> + + <%= link_to t("admin.polls.show.add_question"), + add_question_admin_poll_path(poll_id: @poll.id, question_id: question.id), + method: :patch, + class: "button hollow" %> +
+<% end %> diff --git a/app/views/admin/poll/polls/_subnav.html.erb b/app/views/admin/poll/polls/_subnav.html.erb new file mode 100644 index 000000000..249ff407c --- /dev/null +++ b/app/views/admin/poll/polls/_subnav.html.erb @@ -0,0 +1,63 @@ + diff --git a/app/views/admin/poll/polls/edit.html.erb b/app/views/admin/poll/polls/edit.html.erb new file mode 100644 index 000000000..cf131203f --- /dev/null +++ b/app/views/admin/poll/polls/edit.html.erb @@ -0,0 +1,5 @@ +<%= render 'shared/back_link' %> + +

<%= t("admin.polls.edit.title") %>

+ +<%= render "form" %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/index.html.erb b/app/views/admin/poll/polls/index.html.erb new file mode 100644 index 000000000..5bd310e54 --- /dev/null +++ b/app/views/admin/poll/polls/index.html.erb @@ -0,0 +1,22 @@ +

<%= t("admin.polls.index.title") %>

+ +<%= link_to t("admin.polls.index.create"), + new_admin_poll_path, + class: "button success float-right" %> + +<% if @polls.any? %> + + + + + + + + <%= render @polls %> + +
<%= t("admin.polls.index.name") %><%= t("admin.polls.index.dates") %><%= t("admin.actions.actions") %>
+<% else %> +
+ <%= t("admin.polls.index.no_polls") %> +
+<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/new.html.erb b/app/views/admin/poll/polls/new.html.erb new file mode 100644 index 000000000..ba1ae7260 --- /dev/null +++ b/app/views/admin/poll/polls/new.html.erb @@ -0,0 +1,5 @@ +<%= render 'shared/back_link' %> + +

<%= t("admin.polls.new.title") %>

+ +<%= render "form" %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/search_questions.js.erb b/app/views/admin/poll/polls/search_questions.js.erb new file mode 100644 index 000000000..05f5c5167 --- /dev/null +++ b/app/views/admin/poll/polls/search_questions.js.erb @@ -0,0 +1 @@ +$("#search-questions-results").html("<%= j render 'search_questions_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/polls/show.html.erb b/app/views/admin/poll/polls/show.html.erb new file mode 100644 index 000000000..93ce52e47 --- /dev/null +++ b/app/views/admin/poll/polls/show.html.erb @@ -0,0 +1,8 @@ +<%= render "poll_header" %> + +
+ <%= render "subnav" %> + + <%= render "search_questions" %> + <%= render "questions" %> +
diff --git a/app/views/admin/poll/questions/_filter.html.erb b/app/views/admin/poll/questions/_filter.html.erb new file mode 100644 index 000000000..0fd800067 --- /dev/null +++ b/app/views/admin/poll/questions/_filter.html.erb @@ -0,0 +1,7 @@ +<%= form_tag '', method: :get do %> + <%= label_tag :poll_id, t("admin.questions.index.filter_poll") %> + <%= select_tag "poll_id", + poll_select_options(true), + prompt: t("admin.questions.index.select_poll"), + class: "js-location-changer" %> +<% end %> diff --git a/app/views/admin/poll/questions/_filter_subnav.html.erb b/app/views/admin/poll/questions/_filter_subnav.html.erb new file mode 100644 index 000000000..7f641d390 --- /dev/null +++ b/app/views/admin/poll/questions/_filter_subnav.html.erb @@ -0,0 +1,12 @@ +
    +
  • + <%= link_to "#tab-questions" do %> + <%= t("admin.questions.index.questions_tab") %> + <% end %> +
  • +
  • + <%= link_to "#tab-successful-proposals" do %> + <%= t("admin.questions.index.successful_proposals_tab") %> + <% end %> +
  • +
diff --git a/app/views/admin/poll/questions/_form.html.erb b/app/views/admin/poll/questions/_form.html.erb new file mode 100644 index 000000000..0da36913a --- /dev/null +++ b/app/views/admin/poll/questions/_form.html.erb @@ -0,0 +1,37 @@ +<%= form_for(@question, url: form_url) do |f| %> + + <%= render 'shared/errors', resource: @question %> + + <%= f.hidden_field :proposal_id %> + +
+ +
+
+ <%= f.select :poll_id, + options_for_select(Poll.pluck(:name, :id)), + prompt: t("admin.questions.index.select_poll"), + label: t("admin.questions.new.poll_label") %> +
+ + <%= f.text_field :title, maxlength: Poll::Question.title_max_length %> + + <%= f.label :valid_answers %> +

<%= t("admin.questions.new.valid_answers_note") %>

+ <%= f.text_field :valid_answers, label: false %> + +
+ <%= f.cktext_area :description, + maxlength: Poll::Question.description_max_length, + ckeditor: { language: I18n.locale } %> +
+ +
+
+ <%= f.submit(class: "button expanded", value: t("shared.save")) %> +
+
+
+
+ +<% end %> diff --git a/app/views/admin/poll/questions/_questions.html.erb b/app/views/admin/poll/questions/_questions.html.erb new file mode 100644 index 000000000..c142ad084 --- /dev/null +++ b/app/views/admin/poll/questions/_questions.html.erb @@ -0,0 +1,31 @@ +
+ <%= render 'filter' %> +
+ +<% if @questions.count == 0 %> +
+ <%= t('admin.questions.index.no_questions') %> +
+<% else %> + + + + + + + + + <% @questions.each do |question| %> + + + + + <% end %> + +
<%= t('admin.questions.index.table_question') %><%= t("admin.actions.actions") %>
<%= link_to question.title, admin_question_path(question) %> + <%= link_to t('shared.edit'), edit_admin_question_path(question), class: "button hollow" %> + <%= link_to t('shared.delete'), admin_question_path(question), class: "button hollow alert", method: :delete %> +
+ + <%= paginate @questions %> +<% end %> diff --git a/app/views/admin/poll/questions/_search.html.erb b/app/views/admin/poll/questions/_search.html.erb new file mode 100644 index 000000000..e8a2c52c5 --- /dev/null +++ b/app/views/admin/poll/questions/_search.html.erb @@ -0,0 +1,10 @@ +<%= form_tag(admin_questions_path, method: :get) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_questions_search.placeholder") %> +
+ <%= submit_tag t("admin.shared.poll_questions_search.button"), class: "button" %> +
+
+<% end %> diff --git a/app/views/admin/poll/questions/_successful_proposals.html.erb b/app/views/admin/poll/questions/_successful_proposals.html.erb new file mode 100644 index 000000000..2b192bccd --- /dev/null +++ b/app/views/admin/poll/questions/_successful_proposals.html.erb @@ -0,0 +1,26 @@ + + + + + + + + + <% @proposals.each do |proposal| %> + + + + + <% end %> + +
<%= t('admin.questions.index.table_proposal') %><%= t("admin.actions.actions") %>
+ <%= link_to proposal.title, proposal_path(proposal) %> +

+ <%= proposal.summary %>
+ <%= proposal.question %> +

+
+ <%= link_to t("admin.questions.index.create_question"), + new_admin_question_path(proposal_id: proposal.id), + class: "button hollow" %> +
diff --git a/app/views/admin/poll/questions/edit.html.erb b/app/views/admin/poll/questions/edit.html.erb new file mode 100644 index 000000000..6c4d24adf --- /dev/null +++ b/app/views/admin/poll/questions/edit.html.erb @@ -0,0 +1,5 @@ +<%= render "shared/back_link" %> + +

<%= t("admin.questions.edit.title") %>

+ +<%= render "form", form_url: admin_question_path(@question) %> \ No newline at end of file diff --git a/app/views/admin/poll/questions/index.html.erb b/app/views/admin/poll/questions/index.html.erb new file mode 100644 index 000000000..f45497af0 --- /dev/null +++ b/app/views/admin/poll/questions/index.html.erb @@ -0,0 +1,22 @@ +

<%= t('admin.questions.index.title') %>

+ +<%= link_to t('admin.questions.index.create'), new_admin_question_path, + class: "button success float-right" %> + +
+
+ <%= render 'search' %> +
+
+ +
+ <%= render "filter_subnav" %> + +
+ <%= render "questions" %> +
+ +
+ <%= render "successful_proposals" %> +
+
diff --git a/app/views/admin/poll/questions/new.html.erb b/app/views/admin/poll/questions/new.html.erb new file mode 100644 index 000000000..830492d6d --- /dev/null +++ b/app/views/admin/poll/questions/new.html.erb @@ -0,0 +1,5 @@ +<%= render "shared/back_link" %> + +

<%= t("admin.questions.new.title") %>

+ +<%= render "form", form_url: admin_questions_path %> \ No newline at end of file diff --git a/app/views/admin/poll/questions/show.html.erb b/app/views/admin/poll/questions/show.html.erb new file mode 100644 index 000000000..a8f25f2bd --- /dev/null +++ b/app/views/admin/poll/questions/show.html.erb @@ -0,0 +1,46 @@ +<%= render "shared/back_link" %> + +<%= link_to t('shared.edit'), edit_admin_question_path(@question), + class: "button hollow float-right" %> + +
+ +
+
+ <%= t("admin.questions.show.title") %> +

<%= @question.title %>

+ + <% if @question.proposal.present? %> +

+ <%= t("admin.questions.show.proposal") %> +
+ <%= link_to @question.proposal.title, proposal_path(@question.proposal) %> +

+ <% end %> + +

+ <%= t("admin.questions.show.author") %> +
+ <%= link_to @question.author.name, user_path(@question.author) %> +

+ +

+ <%= t("admin.questions.show.valid_answers") %> +

+ + <% @question.valid_answers.each do |answer| %> + + <%= answer %> + + <% end %> + +

+ <%= t("admin.questions.show.description") %> +
+ <%= @question.description %> +

+ + + <%= link_to t("admin.questions.show.preview"), question_path(@question) %> +
+
diff --git a/app/views/admin/poll/recounts/index.html.erb b/app/views/admin/poll/recounts/index.html.erb new file mode 100644 index 000000000..98055f553 --- /dev/null +++ b/app/views/admin/poll/recounts/index.html.erb @@ -0,0 +1,58 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + +

<%= t("admin.recounts.index.title") %>

+ + <% if @booth_assignments.empty? %> +
+ <%= t("admin.recounts.index.no_recounts") %> +
+ <% else %> + + + + + + + + + <% @booth_assignments.each do |booth_assignment| %> + <% recount = booth_assignment_sum_recounts(booth_assignment) %> + <% final_recount = booth_assignment_sum_final_recounts(booth_assignment) %> + <% system_count = booth_assignment.voters.size %> + + + + + + + <% end %> + +
<%= t("admin.recounts.index.table_booth_name") %><%= t("admin.recounts.index.table_recounts") %><%= t("admin.recounts.index.table_final_recount") %><%= t("admin.recounts.index.table_system_count") %>
+ + <%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment, anchor: 'tab-recounts') %> + + + <% if recount.present? %> + <%= recount %> + <% else %> + - + <% end %> + + <% if final_recount.present? %> + <%= final_recount %> + <% else %> + - + <% end %> + + <% if system_count.present? %> + <%= system_count %> + <% else %> + 0 + <% end %> +
+ + <%= paginate @booth_assignments %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/poll/results/index.html.erb b/app/views/admin/poll/results/index.html.erb new file mode 100644 index 000000000..9d3a6abef --- /dev/null +++ b/app/views/admin/poll/results/index.html.erb @@ -0,0 +1,50 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + +

<%= t("admin.results.index.title") %>

+ + <% if @partial_results.empty? %> +
+ <%= t("admin.results.index.no_results") %> +
+ <% else %> + + + + + + + + + + + + +
<%= t("admin.results.index.table_whites") %><%= t("admin.results.index.table_nulls") %>
<%= @poll.white_results.sum(:amount) %><%= @poll.null_results.sum(:amount) %>
+ + + <% by_question = @partial_results.group_by(&:question_id) %> + <% @poll.questions.each do |question| %> +

<%= question.title %>

+ + + + + + + + + <% question.valid_answers.each_with_index do |answer, i| %> + <% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %> + + + + + <% end %> + +
<%= t("admin.results.index.table_answer") %><%= t("admin.results.index.table_votes") %>
<%= answer %><%= by_answer[answer].present? ? by_answer[answer].sum(&:amount) : 0 %>
+ <% end %> + + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/site_customization/content_blocks/_form.html.erb b/app/views/admin/site_customization/content_blocks/_form.html.erb new file mode 100644 index 000000000..b5a692d87 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/_form.html.erb @@ -0,0 +1,47 @@ +<%= form_for [:admin, @content_block], html: {class: "edit_page", data: {watch_changes: true}} do |f| %> + + <% if @content_block.errors.any? %> + +
+ + + + <%= @content_block.errors.count %> + <%= t("admin.site_customization.content_blocks.errors.form.error", count: @content_block.errors.count) %> + +
+ + <% end %> + +
+ <%= f.label :name %> + <%= f.select :name, SiteCustomization::ContentBlock::VALID_BLOCKS, label: false %> +
+
+ <%= f.label :locale %> + <%= f.select :locale, I18n.available_locales, label: false %> +
+ +
+ <%= f.label :body %> + <%= f.text_area :body, label: false, rows: 10 %> + <%= f.submit class: "button success" %> +
+ +<% end %> + +

<%= t('.content_blocks_information') %>

+ +

<%= t('.content_block_about') %>

+ +

<%= t('.content_block_top_links_html') %>

+
+<li><a href="http://site1.com">Site 1</a></li>
+<li><a href="http://site2.com">Site 2</a></li>
+<li><a href="http://site3.com">Site 3</a></li>
+
+
+ +

<%= t('.content_block_footer_html') %>

diff --git a/app/views/admin/site_customization/content_blocks/edit.html.erb b/app/views/admin/site_customization/content_blocks/edit.html.erb new file mode 100644 index 000000000..c96f9a810 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/edit.html.erb @@ -0,0 +1,15 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.content_blocks") %> - <%= @content_block.name %> (<%= @content_block.locale %>) +<% end %> + +<%= link_to admin_site_customization_content_blocks_path, class: "back" do %> + + <%= t("admin.site_customization.content_blocks.edit.back") %> +<% end %> + +<%= button_to t("admin.site_customization.content_blocks.index.delete"), admin_site_customization_content_block_path(@content_block), method: :delete, class: "button hollow alert float-right margin-right" %> + +
+

<%= t("admin.site_customization.content_blocks.edit.title") %>

+ <%= render 'form' %> +
diff --git a/app/views/admin/site_customization/content_blocks/index.html.erb b/app/views/admin/site_customization/content_blocks/index.html.erb new file mode 100644 index 000000000..653e3e731 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/index.html.erb @@ -0,0 +1,29 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.content_blocks") %> +<% end %> + +<%= link_to t("admin.site_customization.content_blocks.index.create"), new_admin_site_customization_content_block_path, class: "button float-right margin-right" %> +

<%= t("admin.site_customization.content_blocks.index.title") %>

+ + + + + + + + + + + <% @content_blocks.each do |content_block| %> + + + + + + <% end %> + +
<%= t("admin.site_customization.content_blocks.content_block.name") %><%= t("admin.site_customization.content_blocks.content_block.body") %>
<%= link_to "#{content_block.name} (#{content_block.locale})", edit_admin_site_customization_content_block_path(content_block) %><%= content_block.body %> + <%= button_to t("admin.site_customization.content_blocks.index.delete"), + admin_site_customization_content_block_path(content_block), + method: :delete, class: "button hollow alert" %> +
diff --git a/app/views/admin/site_customization/content_blocks/new.html.erb b/app/views/admin/site_customization/content_blocks/new.html.erb new file mode 100644 index 000000000..75ff5f389 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/new.html.erb @@ -0,0 +1,14 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.content_blocks") %> - <%= t("admin.site_customization.content_blocks.new.title") %> +<% end %> + +<%= link_to admin_site_customization_content_blocks_path, class: "back" do %> + + <%= t("admin.site_customization.content_blocks.new.back") %> +<% end %> + +
+

<%= t("admin.site_customization.content_blocks.new.title") %>

+ <%= render 'form' %> +
+ diff --git a/app/views/admin/site_customization/images/index.html.erb b/app/views/admin/site_customization/images/index.html.erb new file mode 100644 index 000000000..b9de152d1 --- /dev/null +++ b/app/views/admin/site_customization/images/index.html.erb @@ -0,0 +1,25 @@ +

<%= t("admin.site_customization.images.index.title") %>

+ + + + <% @images.each do |image| %> + + + + + <% end %> + +
+ <%= image.name %> (<%= image.required_width %>x<%= image.required_height %>) + + <%= form_for([:admin, image], html: { id: "edit_#{dom_id(image)}"}) do |f| %> +
+ <%= image_tag image.image.url if image.image.exists? %> + <%= f.file_field :image, label: false %> +
+
+ <%= f.submit(t('admin.site_customization.images.index.update'), class: "button hollow") %> + <%= link_to t('admin.site_customization.images.index.delete'), admin_site_customization_image_path(image), method: :delete, class: "button hollow alert" if image.image.exists? %> +
+ <% end %> +
diff --git a/app/views/admin/site_customization/pages/_form.html.erb b/app/views/admin/site_customization/pages/_form.html.erb new file mode 100644 index 000000000..197520631 --- /dev/null +++ b/app/views/admin/site_customization/pages/_form.html.erb @@ -0,0 +1,52 @@ +<%= form_for [:admin, @page], html: {class: "edit_page", data: {watch_changes: true}} do |f| %> + + <% if @page.errors.any? %> + +
+ + + + <%= @page.errors.count %> + <%= t("admin.site_customization.pages.errors.form.error", count: @page.errors.count) %> + +
+ + <% end %> + +
+ + <%= f.label :title %> + <%= f.text_field :title, label: false %> + + <%= f.label :subtitle %> + <%= f.text_field :subtitle, label: false, size: 80, maxlength: 80 %> + + <%= f.label :slug %> + <%= f.text_field :slug, label: false, size: 80, maxlength: 80 %> + +
+ <%= f.label :content %> + <%= f.cktext_area :content, label: false, cols: 80, rows: 10, ckeditor: { language: I18n.locale } %> +
+
+
+
+

<%= t("admin.site_customization.pages.form.options") %>

+ <%= f.check_box :more_info_flag %> + <%= f.check_box :print_content_flag %> +
+ +

<%= f.label :status %>

+ <% ::SiteCustomization::Page::VALID_STATUSES.each do |status| %> + <%= f.radio_button :status, status, label: false %> + <%= f.label "status_#{status}", t("admin.site_customization.pages.page.status_#{status}") %> +
+ <% end %> + + <%= f.submit class: "button success" %> +
+ +
+<% end %> diff --git a/app/views/admin/site_customization/pages/edit.html.erb b/app/views/admin/site_customization/pages/edit.html.erb new file mode 100644 index 000000000..c92933cbf --- /dev/null +++ b/app/views/admin/site_customization/pages/edit.html.erb @@ -0,0 +1,15 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.pages") %> - <%= @page.title %> +<% end %> + +<%= link_to admin_site_customization_pages_path, class: "back" do %> + + <%= t("admin.site_customization.pages.edit.back") %> +<% end %> + +<%= button_to t("admin.site_customization.pages.index.delete"), admin_site_customization_page_path(@page), method: :delete, class: "button hollow alert float-right margin-right" %> + +
+

<%= t("admin.site_customization.pages.edit.title", page_title: @page.title) %>

+ <%= render 'form' %> +
diff --git a/app/views/admin/site_customization/pages/index.html.erb b/app/views/admin/site_customization/pages/index.html.erb new file mode 100644 index 000000000..eef3370e6 --- /dev/null +++ b/app/views/admin/site_customization/pages/index.html.erb @@ -0,0 +1,43 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.pages") %> +<% end %> + +<%= link_to t("admin.site_customization.pages.index.create"), new_admin_site_customization_page_path, class: "button float-right margin-right" %> +

<%= t("admin.site_customization.pages.index.title") %>

+ +

<%= page_entries_info @pages %>

+ + + + + + + + + + + + + + <% @pages.each do |page| %> + + + + + + + + + <% end %> + +
<%= t("admin.site_customization.pages.page.title") %><%= t("admin.site_customization.pages.page.created_at") %><%= t("admin.site_customization.pages.page.updated_at") %><%= t("admin.site_customization.pages.page.status") %>
+ <%= link_to page.title, edit_admin_site_customization_page_path(page) %> + <%= I18n.l page.created_at, format: :short %><%= I18n.l page.created_at, format: :short %><%= t("admin.site_customization.pages.page.status_#{page.status}") %> + + <%= link_to t("admin.site_customization.pages.index.see_page"), page.url %> + + + <%= link_to t("admin.site_customization.pages.index.delete"), admin_site_customization_page_path(page), method: :delete %> +
+ + <%= paginate @pages %> diff --git a/app/views/admin/site_customization/pages/new.html.erb b/app/views/admin/site_customization/pages/new.html.erb new file mode 100644 index 000000000..43a86d108 --- /dev/null +++ b/app/views/admin/site_customization/pages/new.html.erb @@ -0,0 +1,14 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.pages") %> - <%= t("admin.site_customization.pages.new.title") %> +<% end %> + +<%= link_to admin_site_customization_pages_path, class: "back" do %> + + <%= t("admin.site_customization.pages.new.back") %> +<% end %> + +
+

<%= t("admin.site_customization.pages.new.title") %>

+ <%= render 'form' %> +
+ diff --git a/app/views/admin/stats/show.html.erb b/app/views/admin/stats/show.html.erb index ef17536c3..cd83201a4 100644 --- a/app/views/admin/stats/show.html.erb +++ b/app/views/admin/stats/show.html.erb @@ -11,7 +11,8 @@ direct_messages_admin_stats_path, class: "button hollow" %> <%= link_to t("admin.stats.show.proposal_notifications"), proposal_notifications_admin_stats_path, class: "button hollow" %> - + <%= link_to t("admin.stats.show.incomplete_verifications"), + admin_verifications_path, class: "button hollow" %>
diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb index 73fc3a697..f3e386d27 100644 --- a/app/views/budgets/index.html.erb +++ b/app/views/budgets/index.html.erb @@ -10,8 +10,10 @@
- - + + + + <% @budgets.each do |budget| %> diff --git a/app/views/budgets/investments/_investment_show.html.erb b/app/views/budgets/investments/_investment_show.html.erb index ebac39186..d97a1fbfb 100644 --- a/app/views/budgets/investments/_investment_show.html.erb +++ b/app/views/budgets/investments/_investment_show.html.erb @@ -103,7 +103,7 @@ + <% end %> <%= render "devise/shared/links" %> diff --git a/app/views/devise/menu/_login_items.html.erb b/app/views/devise/menu/_login_items.html.erb index 0ab28e2ee..b7db4a22c 100644 --- a/app/views/devise/menu/_login_items.html.erb +++ b/app/views/devise/menu/_login_items.html.erb @@ -1,6 +1,6 @@ <% if user_signed_in? %>
  • - <%= link_to notifications_path, class: "notifications", accesskey: "n" do %> + <%= link_to notifications_path, class: "notifications" do %> <%= t("layouts.header.notifications") %> <% if current_user.notifications_count > 0 %> @@ -12,19 +12,19 @@ <% end %>
  • - <%= link_to(t("layouts.header.my_activity_link"), user_path(current_user), accesskey: "a") %> + <%= link_to t("layouts.header.my_activity_link"), user_path(current_user) %>
  • - <%= link_to(t("layouts.header.my_account_link"), account_path, accesskey: "m") %> + <%= link_to t("layouts.header.my_account_link"), account_path %>
  • - <%= link_to(t("devise_views.menu.login_items.logout"), destroy_user_session_path, method: :delete) %> + <%= link_to t("devise_views.menu.login_items.logout"), destroy_user_session_path, method: :delete %>
  • <% else %>
  • - <%= link_to(t("devise_views.menu.login_items.login"), new_user_session_path) %> + <%= link_to t("devise_views.menu.login_items.login"), new_user_session_path %>
  • - <%= link_to(t("devise_views.menu.login_items.signup"), new_user_registration_path, class: "button") %> + <%= link_to t("devise_views.menu.login_items.signup"), new_user_registration_path, class: "button" %>
  • <% end %> diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb index 72dd70fb9..b06c9ff25 100644 --- a/app/views/devise/passwords/new.html.erb +++ b/app/views/devise/passwords/new.html.erb @@ -11,6 +11,7 @@ <%= f.submit t("devise_views.passwords.new.send_submit"), class: "button expanded" %> + <% end %> <%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index ad375e772..910135853 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -11,18 +11,18 @@ <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
    - <%= f.email_field :email, autofocus: true, placeholder: t("devise_views.sessions.new.email_label"), tabindex: "1" %> + <%= f.email_field :email, autofocus: true, placeholder: t("devise_views.sessions.new.email_label") %>
    - <%= link_to t("devise_views.shared.links.new_password"), new_password_path(resource_name), class: "float-right", tabindex: "3" %> - <%= f.password_field :password, autocomplete: "off", placeholder: t("devise_views.sessions.new.password_label"), tabindex: "2" %> + <%= f.password_field :password, autocomplete: "off", placeholder: t("devise_views.sessions.new.password_label") %> + <%= link_to t("devise_views.shared.links.new_password"), new_password_path(resource_name), class: "float-right" %>
    <% if devise_mapping.rememberable? -%>
    <%= f.label :remember_me do %> - <%= f.check_box :remember_me, title: t('devise_views.sessions.new.remember_me'), label: false, tabindex: "4" %> + <%= f.check_box :remember_me, title: t('devise_views.sessions.new.remember_me'), label: false %> <%= t("devise_views.sessions.new.remember_me") %> <% end %>
    @@ -35,5 +35,3 @@ <% end %> <%= render "devise/shared/links" %> - - diff --git a/app/views/layouts/_admin_header.html.erb b/app/views/layouts/_admin_header.html.erb index 095e351d6..8597b41de 100644 --- a/app/views/layouts/_admin_header.html.erb +++ b/app/views/layouts/_admin_header.html.erb @@ -1,14 +1,16 @@
    - +
    - <%= link_to setting['org_name'], admin_root_path, class: "logo show-for-small-only" %> + <%= link_to setting['org_name'], namespaced_root_path, class: "logo show-for-small-only" %> @@ -17,11 +19,13 @@
    - <%= link_to admin_root_path, class: "hide-for-small-only" do %> - <%= image_tag('logo_header.png', class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> - <%= setting['org_name'] %> -  | <%= t("admin.dashboard.index.title") %> - <% end %> +

    + <%= link_to namespaced_root_path, class: "hide-for-small-only" do %> + <%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> + <%= setting['org_name'] %> +  | <%= namespaced_header_title %> + <% end %> +

    @@ -34,4 +38,4 @@
    - \ No newline at end of file + diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb index 4f8545fb5..0bce1cd67 100644 --- a/app/views/layouts/_footer.html.erb +++ b/app/views/layouts/_footer.html.erb @@ -1,5 +1,5 @@
    -
    +

    <%= link_to t("layouts.header.open_gov", open: "#{t('layouts.header.open')}").html_safe %> @@ -25,14 +25,14 @@

    - <%= link_to t("layouts.footer.transparency_title"), t("layouts.footer.transparency_url") %> + <%= link_to t("layouts.footer.transparency_title"), setting['transparency_url'].presence || t("layouts.footer.transparency_url") %>

    <%= t("layouts.footer.transparency_text") %>

    - <%= link_to t("layouts.footer.open_data_title"), "/opendata" %> + <%= link_to t("layouts.footer.open_data_title"), setting['opendata_url'].presence || t("layouts.header.external_link_opendata_url") %>

    <%= t("layouts.footer.open_data_text") %>

    @@ -43,7 +43,6 @@
    <%= t("layouts.footer.copyright", year: Time.current.year) %> |
      -
    • <%= link_to t("layouts.footer.more_info"), page_path('more_information') %> |
    • <%= link_to t("layouts.footer.privacy"), page_path('privacy') %> |
    • <%= link_to t("layouts.footer.conditions"), page_path('conditions') %> |
    • <%= link_to t("layouts.footer.accessibility"), page_path('accessibility') %>
    • @@ -85,8 +84,20 @@ <% end %> <% end %> + <% if setting['telegram_handle'] %> +
    • + <%= link_to "https://www.telegram.me/#{setting['telegram_handle']}", target: "_blank", title: t("social.telegram") + t('shared.target_blank_html') do %> + <%= t("social.telegram") %> + + <% end %> +
    • + <% end %>

    + +
    + <%= raw content_block("footer", I18n.locale) %> +
    diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 223f173b2..a3162bb8c 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -21,8 +21,8 @@
    - <%= link_to root_path, class: "hide-for-small-only", accesskey: "/" do %> - <%= image_tag('logo_header.png', class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> + <%= link_to root_path, class: "hide-for-small-only", accesskey: "0" do %> + <%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> <%= setting['org_name'] %> <% end %>
    diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 95bcae3e3..198bf3adf 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -30,7 +30,7 @@
    <%= render 'layouts/admin_header' %> -
    +
    @@ -42,11 +42,11 @@
    -
    +
    <%= render 'layouts/flash' %> <%= yield %>
    -
    +
    diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7a1dae4d9..7f66b524e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -14,18 +14,20 @@ <%= javascript_include_tag "application", 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <%= favicon_link_tag "favicon.ico" %> - <%= favicon_link_tag "apple-touch-icon-200.png", + <%= favicon_link_tag image_path_for("apple-touch-icon-200.png"), rel: "icon apple-touch-icon", sizes: "200x200", type: "image/png" %> <%= content_for :social_media_meta_tags %> - <%= setting['per_page_code'].try(:html_safe) %> + <%= setting['per_page_code_head'].try(:html_safe) %> + <%= setting['per_page_code_body'].try(:html_safe) %> +

    <%= setting['org_name'] %>

    -
    +
    <%= render 'layouts/header' %> +
    +
    + + <%= select_tag :officer_assignment_id, + officer_assignments_select_options(@officer_assignments), + { prompt: t("officing.recounts.new.select_booth_date"), + label: false } %> +
    +
    + +
    +
    + + <%= text_field_tag :count, nil, placeholder: t("officing.recounts.new.count_placeholder") %> +
    +
    + +
    +
    + <%= submit_tag t("officing.recounts.new.submit"), class: "button expanded" %> +
    +
    + <% end %> +<% else %> +

    <%= @poll.name %>

    +
    + <%= t("officing.recounts.new.not_allowed") %> +
    +<% end %> + + +<% if @recounted.any? %> +
    +

    <%= t("officing.recounts.new.recount_list") %>

    + +
    <%= Budget.human_attribute_name(:name) %><%= Budget.human_attribute_name(:phase) %>
    <%= Budget.human_attribute_name(:name) %><%= Budget.human_attribute_name(:phase) %>
    + + + + + + + <% @recounted.each do |oa| %> + + + + + + <% end %> + +
    <%= t("officing.recounts.new.date") %><%= t("officing.recounts.new.booth") %><%= t("officing.recounts.new.count") %>
    + <%= l(oa.date.to_date, format: :long) %> + + <%= oa.booth_assignment.booth.name %> + + <%= oa.recount.count %> +
    +<% end %> diff --git a/app/views/officing/residence/_errors.html.erb b/app/views/officing/residence/_errors.html.erb new file mode 100644 index 000000000..4f8895a35 --- /dev/null +++ b/app/views/officing/residence/_errors.html.erb @@ -0,0 +1,14 @@ +<% if @residence.errors[:residence_in_madrid].present? %> + +
    + + <%= t("officing.residence.new.error_verifying_census") %> +
    + +<% else %> + <%= render "shared/errors", + resource: @residence, + message: t("officing.residence.new.form_errors") %> +<% end %> diff --git a/app/views/officing/residence/new.html.erb b/app/views/officing/residence/new.html.erb new file mode 100644 index 000000000..a508fae52 --- /dev/null +++ b/app/views/officing/residence/new.html.erb @@ -0,0 +1,32 @@ +

    <%= t("officing.residence.new.title") %>

    + +<% if @officer_assignments.present? %> + +<% else %> +
    + <%= t("officing.residence.new.no_assignments") %> +
    +<% end %> diff --git a/app/views/officing/results/index.html.erb b/app/views/officing/results/index.html.erb new file mode 100644 index 000000000..70da6d8d3 --- /dev/null +++ b/app/views/officing/results/index.html.erb @@ -0,0 +1,57 @@ +<%= back_link_to new_officing_poll_result_path(@poll) %> +

    <%= @poll.name %> - <%= t("officing.results.index.results") %>

    + +<% if @partial_results.present? %> +
    +

    + <%= @booth_assignment.booth.name %> - <%= l @partial_results.first.date, format: :long %> +

    +
    + + +
    +
    + + + + + + + + + + + + + +
    <%= t("officing.results.index.table_whites") %><%= t("officing.results.index.table_nulls") %>
    <%= @whites %><%= @nulls %>
    + + <% by_question = @partial_results.group_by(&:question_id) %> + <% @poll.questions.each do |question| %> +

    <%= question.title %>

    + + + + + + + + + + <% question.valid_answers.each_with_index do |answer, i| %> + <% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %> + + + + + <% end %> + +
    <%= t("officing.results.index.table_answer") %><%= t("officing.results.index.table_votes") %>
    <%= answer %><%= by_answer[answer].present? ? by_answer[answer].first.amount : 0 %>
    + <% end %> +
    +
    +<% else %> +
    + <%= t("officing.results.index.no_results") %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/officing/results/new.html.erb b/app/views/officing/results/new.html.erb new file mode 100644 index 000000000..74af8af10 --- /dev/null +++ b/app/views/officing/results/new.html.erb @@ -0,0 +1,99 @@ +<% if @officer_assignments.any? %> +

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

    + + <%= form_tag(officing_poll_results_path(@poll), {id: "officer_assignment_form"}) do %> +
    +
    + + <%= select_tag :officer_assignment_id, + booths_for_officer_select_options(@officer_assignments), + { prompt: t("officing.results.new.select_booth"), + label: false } %> +
    +
    + +
    +
    + + <%= select_tag :date, + poll_dates_select_options(@poll), + { prompt: t("officing.results.new.select_date"), + label: false } %> +
    +
    + + <% @poll.questions.each do |question| %> +
    +
    +

    <%= question.title %>

    +
    + <% question.valid_answers.each_with_index do |answer, i| %> +
    + + <%= text_field_tag "questions[#{question.id}][#{i}]", answer_result_value(question.id, i), placeholder: "0" %> +
    + <% end %> +
    +
    + <% end %> + +
    +
    +

    <%= t("officing.results.new.ballots_white") %>

    + <%= text_field_tag :whites, params[:whites].presence, placeholder: "0" %> +
    + +
    +

    <%= t("officing.results.new.ballots_null") %>

    + <%= text_field_tag :nulls, params[:nulls].presence, placeholder: "0" %> +
    +
    +
    + +
    +
    + <%= submit_tag t("officing.results.new.submit"), class: "button expanded" %> +
    +
    + <% end %> + +<% else %> +

    <%= @poll.name %>

    +
    + <%= t("officing.results.new.not_allowed") %> +
    +<% end %> + +<% if @partial_results.present? %> + +
    +

    <%= t("officing.results.new.results_list") %>

    + + + + + + + + + <% results_by_booth = @partial_results.group_by(&:booth_assignment_id) %> + <% results_by_booth.keys.each do |booth_assignment| %> + <% results_by_booth[booth_assignment].group_by(&:date).keys.each do |date| %> + + + + + + <% end %> + <% end %> + +
    <%= t("officing.results.new.date") %><%= t("officing.results.new.booth") %> 
    + <%= l(date, format: :long) %> + + <%= results_by_booth[booth_assignment].first.booth_assignment.booth.name %> + + <%= link_to t("officing.results.new.see_results"), officing_poll_results_path(@poll, date: l(date), booth_assignment_id: booth_assignment) %> +
    + +<% end %> + diff --git a/app/views/officing/voters/_already_voted.html.erb b/app/views/officing/voters/_already_voted.html.erb new file mode 100644 index 000000000..1476913f9 --- /dev/null +++ b/app/views/officing/voters/_already_voted.html.erb @@ -0,0 +1,7 @@ +
    +
    +
    + <%= t("officing.voters.show.error_already_voted") %> +
    +
    +
    diff --git a/app/views/officing/voters/_can_vote.html.erb b/app/views/officing/voters/_can_vote.html.erb new file mode 100644 index 000000000..27c5e9dbc --- /dev/null +++ b/app/views/officing/voters/_can_vote.html.erb @@ -0,0 +1,14 @@ +
    +
    +
    + <%= t("officing.voters.show.can_vote") %> +
    +
    +
    + <%= form_for @user, as: :voter, url: officing_voters_path, method: :post, remote: true do |f| %> + <%= f.hidden_field :poll_id, value: poll.id %> + <%= f.hidden_field :user_id, value: @user.id %> + <%= f.submit t("officing.voters.show.submit"), class: "button success expanded" %> + <% end %> +
    +
    diff --git a/app/views/officing/voters/_voted.html.erb b/app/views/officing/voters/_voted.html.erb new file mode 100644 index 000000000..edd098403 --- /dev/null +++ b/app/views/officing/voters/_voted.html.erb @@ -0,0 +1,7 @@ +
    +
    +
    + <%= t("officing.voters.show.success") %> +
    +
    +
    diff --git a/app/views/officing/voters/create.js.erb b/app/views/officing/voters/create.js.erb new file mode 100644 index 000000000..ee280faa2 --- /dev/null +++ b/app/views/officing/voters/create.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@poll) %> #actions").html('<%= j render("voted") %>'); \ No newline at end of file diff --git a/app/views/officing/voters/new.html.erb b/app/views/officing/voters/new.html.erb new file mode 100644 index 000000000..0f8fa2958 --- /dev/null +++ b/app/views/officing/voters/new.html.erb @@ -0,0 +1,30 @@ +<%= back_link_to new_officing_residence_path %> + +

    <%= t("officing.voters.new.title") %>

    + +<% if @polls.any? %> + + + + + + + + + <% @polls.each do |poll| %> + + + + + <% end %> + +
    <%= t("officing.voters.new.table_poll") %><%= t("officing.voters.new.table_actions") %>
    + <%= poll.name %> + + <% if poll.votable_by?(@user) %> + <%= render "can_vote", poll: poll %> + <% else %> + <%= render "already_voted" %> + <% end %> +
    +<% end %> \ No newline at end of file diff --git a/app/views/pages/accessibility.html.erb b/app/views/pages/accessibility.html.erb index a2c2eff30..a0f54760d 100644 --- a/app/views/pages/accessibility.html.erb +++ b/app/views/pages/accessibility.html.erb @@ -19,45 +19,34 @@
    + - - + + - + - + - + - + + + + + - - - - - - - - - - - - - - - -
    Atajos de teclado para el menú de navegación
    TeclaPáginaTeclaPágina
    /0 Inicio
    D1 Debates
    P2 Propuestas
    S3Votaciones
    4 Presupuestos participativos
    IMás información
    NNotificaciones
    AMi actividad
    MMi cuenta
    @@ -66,10 +55,11 @@
    + - - + + @@ -104,8 +94,8 @@
    Combinación de teclas dependiendo del sistema operativo y navegador
    NavegadorCombinación de teclasNavegadorCombinación de teclas
    - - + + diff --git a/app/views/pages/custom_page.html.erb b/app/views/pages/custom_page.html.erb new file mode 100644 index 000000000..f14eba7d9 --- /dev/null +++ b/app/views/pages/custom_page.html.erb @@ -0,0 +1,15 @@ +<% provide :title do %><%= @custom_page.title %><% end %> + +
    + +
    +

    <%= @custom_page.title %>

    +

    <%= @custom_page.subtitle %>

    + + <%= raw @custom_page.content %> +
    + +
    + <%= render '/shared/print' if @custom_page.print_content_flag %> +
    +
    diff --git a/app/views/pages/how_it_works.html.erb b/app/views/pages/how_it_works.html.erb deleted file mode 100644 index 67fb38371..000000000 --- a/app/views/pages/how_it_works.html.erb +++ /dev/null @@ -1,20 +0,0 @@ -
    - - -
    -

    <%= t('pages.more_information.titles.how_it_works') %>

    - <%= markdown t('pages.more_information.how_it_works.text') %> -
    -
    diff --git a/app/views/pages/how_to_use.html.erb b/app/views/pages/how_to_use.html.erb deleted file mode 100644 index a594961a8..000000000 --- a/app/views/pages/how_to_use.html.erb +++ /dev/null @@ -1,9 +0,0 @@ -
    -
    - <%= render "shared/back_link" %> - -

    <%= t('pages.more_information.titles.how_to_use') %>

    - - <%= markdown t('pages.more_information.how_to_use.text') %> -
    -
    diff --git a/app/views/pages/more_info/_budgets.html.erb b/app/views/pages/more_info/_budgets.html.erb new file mode 100644 index 000000000..6450bdffe --- /dev/null +++ b/app/views/pages/more_info/_budgets.html.erb @@ -0,0 +1,23 @@ +
    +
    +

    + <%= t("pages.more_info.budgets.title") %> +

    +

    <%= t("pages.more_info.budgets.description") %>

    +
      +
    • + <%= t("pages.more_info.budgets.feature_1", + link: link_to(t("pages.more_info.budgets.feature_1_link", org_name: setting['org_name']), + new_user_registration_path)).html_safe %> +
    • +
    • <%= t("pages.more_info.budgets.feature_2_html") %>
    • +
    • <%= t("pages.more_info.budgets.feature_3_html") %>
    • +
    • <%= t("pages.more_info.budgets.feature_4_html") %>
    • +
    + +
    + <%= image_tag "more_info/budgets_#{I18n.locale}.png", alt: t("pages.more_info.budgets.image_alt") %> +
    <%= t("pages.more_info.budgets.figcaption_html") %>
    +
    +
    +
    diff --git a/app/views/pages/more_info/_debates.html.erb b/app/views/pages/more_info/_debates.html.erb new file mode 100644 index 000000000..a8e76ba91 --- /dev/null +++ b/app/views/pages/more_info/_debates.html.erb @@ -0,0 +1,21 @@ +
    +
    +

    + <%= t("pages.more_info.debates.title") %> +

    +

    <%= t("pages.more_info.debates.description") %>

    +
      +
    • + <%= t("pages.more_info.debates.feature_1", + link: link_to(t("pages.more_info.debates.feature_1_link", org_name: setting['org_name']), + new_user_registration_path)).html_safe %> +
    • +
    • <%= t("pages.more_info.debates.feature_2_html") %>
    • +
    + +
    + <%= image_tag "more_info/debates.png", alt: t("pages.more_info.debates.image_alt") %> +
    <%= t("pages.more_info.debates.figcaption") %>
    +
    +
    +
    diff --git a/app/views/pages/more_info/_menu.html.erb b/app/views/pages/more_info/_menu.html.erb new file mode 100644 index 000000000..85097e6a8 --- /dev/null +++ b/app/views/pages/more_info/_menu.html.erb @@ -0,0 +1,22 @@ +
    +
    + +
    +
    diff --git a/app/views/pages/more_info/_other.html.erb b/app/views/pages/more_info/_other.html.erb new file mode 100644 index 000000000..5501c9e31 --- /dev/null +++ b/app/views/pages/more_info/_other.html.erb @@ -0,0 +1,11 @@ +

    Otra información de interés

    + +
      +
    • <%= link_to t("pages.more_info.other.how_to_use", org_name: setting['org_name']), faq_path %>
    • +
    • <%= link_to t("pages.more_info.other.facts"), participation_facts_path %>
    • +
    • <%= link_to t("pages.more_info.other.world"), participation_world_path %>
    • + + <% SiteCustomization::Page.with_more_info_flag.each do |custom_page| %> +
    • <%= link_to custom_page.title, page_path(custom_page.slug) %>
    • + <% end %> +
    diff --git a/app/views/pages/more_info/_polls.html.erb b/app/views/pages/more_info/_polls.html.erb new file mode 100644 index 000000000..360558d99 --- /dev/null +++ b/app/views/pages/more_info/_polls.html.erb @@ -0,0 +1,15 @@ +
    +
    +

    <%= t("pages.more_info.polls.title") %>

    +

    <%= t("pages.more_info.polls.description") %>

    +
      +
    • + <%= t("pages.more_info.polls.feature_1", + link: link_to(t("pages.more_info.polls.feature_1_link", org_name: setting['org_name']), + new_user_registration_path)).html_safe %> +
    • +
    • <%= t("pages.more_info.polls.feature_2") %>
    • +
    • <%= t("pages.more_info.polls.feature_3") %>
    • +
    +
    +
    diff --git a/app/views/pages/more_info/_proposals.html.erb b/app/views/pages/more_info/_proposals.html.erb new file mode 100644 index 000000000..cf1822142 --- /dev/null +++ b/app/views/pages/more_info/_proposals.html.erb @@ -0,0 +1,22 @@ +
    +
    +

    + <%= t("pages.more_info.proposals.title") %> +

    +

    <%= t("pages.more_info.proposals.description") %>

    +
      +
    • + <%= t("pages.more_info.proposals.feature_1", + link: link_to(t("pages.more_info.proposals.feature_1_link", org_name: setting['org_name']), + new_user_registration_path)).html_safe %> +
    • +
    • <%= t("pages.more_info.proposals.feature_2_html") %>
    • +
    • <%= t("pages.more_info.proposals.feature_3_html") %>
    • +
    + +
    + <%= image_tag "more_info/proposals_#{I18n.locale}.png", alt: t("pages.more_info.proposals.image_alt") %> +
    <%= t("pages.more_info.proposals.figcaption_html") %>
    +
    +
    +
    diff --git a/app/views/pages/more_info/_sidebar.html.erb b/app/views/pages/more_info/_sidebar.html.erb new file mode 100644 index 000000000..09131685b --- /dev/null +++ b/app/views/pages/more_info/_sidebar.html.erb @@ -0,0 +1,5 @@ + diff --git a/app/views/pages/faq.html.erb b/app/views/pages/more_info/faq/index.html similarity index 100% rename from app/views/pages/faq.html.erb rename to app/views/pages/more_info/faq/index.html diff --git a/app/views/pages/more_info/how_to_use/index.html.erb b/app/views/pages/more_info/how_to_use/index.html.erb new file mode 100644 index 000000000..9c6ccfb00 --- /dev/null +++ b/app/views/pages/more_info/how_to_use/index.html.erb @@ -0,0 +1,9 @@ +
    +
    + <%= render "shared/back_link" %> + +

    <%= t('pages.more_info.titles.how_to_use') %>

    + + <%= markdown t('pages.more_info.how_to_use.text') %> +
    +
    diff --git a/app/views/pages/more_info/index.html.erb b/app/views/pages/more_info/index.html.erb new file mode 100644 index 000000000..940170ed0 --- /dev/null +++ b/app/views/pages/more_info/index.html.erb @@ -0,0 +1,43 @@ +<% provide :title do %><%= t("pages.titles.more_info", org_name: setting['org_name']) %><% end %> + +
    +
    +
    +

    <%= t("pages.more_info.title", org_name: setting['org_name']) %>

    +

    <%= t("pages.more_info.subtitle") %>

    +

    <%= t("pages.more_info.guide", org_name: setting['org_name']) %>

    +
    +
    + + <%= render "pages/more_info/menu" %> +
    + +
    +
    +
    + <% if feature?(:debates) %> + <%= render "pages/more_info/debates" %> + <% end %> + + <%= render "pages/more_info/proposals" %> + + <% if feature?(:budgets) %> + <%= render "pages/more_info/budgets" %> + <% end %> + + <% if feature?(:polls) %> + <%= render "pages/more_info/polls" %> + <% end %> +
    +
    + <%= render "pages/more_info/sidebar" %> +
    +
    + +
    +
    +
    + <%= render "pages/more_info/other" %> +
    +
    +
    diff --git a/app/views/pages/participation_facts.html.erb b/app/views/pages/more_info/participation/facts.html.erb similarity index 100% rename from app/views/pages/participation_facts.html.erb rename to app/views/pages/more_info/participation/facts.html.erb diff --git a/app/views/pages/participation_world.html.erb b/app/views/pages/more_info/participation/world.html.erb similarity index 100% rename from app/views/pages/participation_world.html.erb rename to app/views/pages/more_info/participation/world.html.erb diff --git a/app/views/pages/more_information.html.erb b/app/views/pages/more_information.html.erb deleted file mode 100644 index 07105ef0f..000000000 --- a/app/views/pages/more_information.html.erb +++ /dev/null @@ -1,71 +0,0 @@ -<% provide :title do %><%= t('pages.titles.more_information') %><% end %> -
    -
    -

    <%= t('pages.titles.more_information') %>

    -
      -
    • - <%= link_to page_path('how_it_works') do %> - <%= t('pages.more_information.titles.how_it_works') %> -
      - <%= t('pages.more_information.description.how_it_works') %> - <% end %> -
    • -
    • - <%= link_to page_path('how_to_use') do %> - <%= t('pages.more_information.titles.how_to_use') %> -
      - <%= t('pages.more_information.description.how_to_use') %> - <% end %> -
    • -
    • - <%= link_to page_path('participation') do %> - <%= t('pages.more_information.titles.participation') %> -
      - <%= t('pages.more_information.description.participation') %> - <% end %> -
    • -
    • - <%= link_to page_path('proposals_info') do %> - <%= t('pages.more_information.titles.proposals_info') %> -
      - <%= t('pages.more_information.description.proposals_info') %> - <% end %> -
    • -
    • - <%= link_to page_path('spending_proposals_info') do %> - <%= t('pages.more_information.titles.spending_proposals_info') %> -
      - <%= t('pages.more_information.description.spending_proposals_info') %> - <% end %> -
    • -
    • - <%= link_to page_path('participation_world') do %> - <%= t('pages.more_information.titles.participation_world') %> -
      - <%= t('pages.more_information.description.participation_world') %> - <% end %> -
    • -
    • - <%= link_to page_path('participation_facts') do %> - <%= t('pages.more_information.titles.participation_facts') %> -
      - <%= t('pages.more_information.description.participation_facts') %> - <% end %> -
    • -
    • - <%= link_to page_path('faq') do %> - <%= t('pages.more_information.titles.faq') %> -
      - <%= t('pages.more_information.description.faq') %> - <% end %> -
    • -
    • - <%= link_to page_path('proposals_info', :anchor => "iii") do %> - <%= t('pages.more_information.titles.signature_sheet') %> -
      - <%= t('pages.more_information.description.signature_sheet') %> - <% end %> -
    • -
    -
    -
    diff --git a/app/views/pages/opendata.html.erb b/app/views/pages/opendata.html.erb deleted file mode 100644 index aeee6b465..000000000 --- a/app/views/pages/opendata.html.erb +++ /dev/null @@ -1,14 +0,0 @@ -<% provide :title do %><%= t('pages.titles.opendata') %><% end %> - -
    -
    -
    -

    <%= t("pages.opendata.title") %>

    -

    <%= t("pages.opendata.slogan") %>

    -
    - -
    - <%= image_tag("icon_home.png", size: "330x240", alt:"") %> -
    -
    -
    diff --git a/app/views/pages/participation.html.erb b/app/views/pages/participation.html.erb deleted file mode 100644 index 9d92140fb..000000000 --- a/app/views/pages/participation.html.erb +++ /dev/null @@ -1,46 +0,0 @@ -
    - - -
    -

    Participación y Transparencia

    - -

    Página de información sobre Participación y Transparencia.

    - -

    I. Participación.

    - -
      -
    • I.1. Participación. Información sobre Participación
    • -
    • I.1. Participación. Información sobre Participación
    • -
    • I.1. Participación. Información sobre Participación
    • -
    - -

    II. Transparencia

    - -
      -
    • II.1. Transparencia Información sobre Transparencia
    • -
    • II.1. Transparencia Información sobre Transparencia
    • -
    • II.1. Transparencia Información sobre Transparencia
    • -
    -
    -
    diff --git a/app/views/pages/proposals_info.html.erb b/app/views/pages/proposals_info.html.erb deleted file mode 100644 index 09bf8585e..000000000 --- a/app/views/pages/proposals_info.html.erb +++ /dev/null @@ -1,28 +0,0 @@ -
    -
    - <%= render "shared/back_link" %> - - -
    - -
    -

    ¿Cómo funcionan las propuestas ciudadanas?

    -

    El mecanismo de propuestas ciudadanas se resume en cuatro pasos muy sencillos:

    -
      -
    1. ¡Propones! Creas una propuesta en esta web.
    2. -
    3. ¡Apoyas! La gente hace clic en el botón de apoyar tu propuesta (necesitas el apoyo del 2% de los empadronados mayores de 16 años para pasar a la siguiente fase).
    4. -
    5. ¡Decides! Si has conseguido suficientes apoyos, dejamos 45 días para que la gente pueda debatir sobre la propuesta, y después durante una semana se invita a toda la gente empadronada a decidir si están a favor o en contra de tu propuesta, en esta misma web.
    6. -
    7. ¡Se hace! Si hay más gente a favor de tu propuesta que en contra, el gobierno del Ayuntamiento asumirá como propia la propuesta y la llevará a cabo.
    8. -
    - -

    Explicación detallada del proceso

    -
      -
    1. Creación de una propuesta. Cualquier persona (sin necesidad siquiera de estar empadronada) puede crear una propuesta. Lo único que hay que hacer es pulsar el botón “Crear una propuesta” y rellenar los campos requeridos. La propuesta puede ser tan sencilla como una simple frase, pero te recomendamos detallarla todo lo posible, incluso añadiendo material adicional, para que sea más completa e interesante. Una vez creada aparecerá en esta web para que cualquiera pueda apoyarla.
    2. -
    3. Apoyo de propuestas. Para apoyar una de las propuestas que aparece en la web, pulsamos el botón “apoyar esta propuesta” que aparece en cada una. Para este paso tendremos que estar empadronados, así que al llevarlo a cabo por primera vez se nos pedirá que verifiquemos nuestra cuenta para estar seguros de este requerimiento. Se nos va a pedir que introduzcamos algunos datos para comprobar nuestra información de empadronamiento, y se nos enviará un código personal para que el proceso sea seguro. Las propuestas necesitan una cierta cantidad de apoyos para pasar a la siguiente fase; concretamente el 2% de los empadronados mayores de 16 años.
    4. -
    5. Decisión sobre propuestas. Cuando una propuesta consigue los apoyos necesarios, se anuncia en la web. Desde ese momento se dejan 45 días para que todo el mundo pueda debatir e informarse sobre la propuesta. Todas las otras propuestas que hayan conseguido los apoyos necesarios en los primeros 30 días del tiempo de debate se agruparán junto a la primera para decidir sobre ellas al mismo tiempo. Pasados los 45 días se exponen estas propuestas en un espacio especial de votación de la web, donde durante una semana cualquier persona empadronada y mayor de 16 años podrá decidir si está a favor o rechaza la propuesta. Para participar en este paso tendrás que tener tu cuenta de usuario verificada completamente, de tal forma que nos aseguremos que cada persona no tiene más de una cuenta y el proceso es seguro.
    6. -
    7. Realización de las propuestas. En caso de que haya más gente a favor de una propuesta que rechazándola se aceptará como propuesta colectiva de la ciudadanía, y el gobierno del Ayuntamiento la asumirá como propia y la llevará a cabo. Para ello en un plazo máximo de un mes, se realizarán los informes técnicos correspondientes sobre su legalidad, viabilidad y coste económico, teniendo en cuenta a los sectores afectados y a la persona que haya lanzado la propuesta, para detallar la actuación correspondiente por parte del Ayuntamiento. Se publicarán en la web todos los informes realizados, y un seguimiento de las actuaciones que se lleven a cabo, para asegurar un correcto desarrollo de la propuesta.
    8. -
    -
    -
    diff --git a/app/views/pages/spending_proposals_info.html.erb b/app/views/pages/spending_proposals_info.html.erb deleted file mode 100644 index 41332bb96..000000000 --- a/app/views/pages/spending_proposals_info.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -
    -
    - <%= render "shared/back_link" %> - - -
    - -
    -

    ¿Cómo funcionan los Presupuestos participativos?

    -

    Explicación detallada del proceso

    -

    Descripción del proceso de participación ciudadana en los presupuestos.

    -
    -
    diff --git a/app/views/polls/_callout.html.erb b/app/views/polls/_callout.html.erb new file mode 100644 index 000000000..2fad7d07d --- /dev/null +++ b/app/views/polls/_callout.html.erb @@ -0,0 +1,22 @@ +<% unless can?(:answer, @poll) %> + <% if current_user.nil? %> +
    + <%= t("polls.show.cant_answer_not_logged_in", + signin: link_to(t("polls.show.signin"), new_user_session_path, class: "probe-message"), + signup: link_to(t("polls.show.signup"), new_user_registration_path, class: "probe-message")).html_safe %> +
    + <% elsif current_user.unverified? %> +
    + <%= t('polls.show.cant_answer_verify_html', + verify_link: link_to(t('polls.show.verify_link'), verification_path)) %> +
    + <% elsif @poll.incoming? %> +
    + <%= t('polls.show.cant_answer_incoming') %> +
    + <% elsif @poll.expired? %> +
    + <%= t('polls.show.cant_answer_expired') %> +
    + <% end %> +<% end %> diff --git a/app/views/polls/_poll_group.html.erb b/app/views/polls/_poll_group.html.erb new file mode 100644 index 000000000..f4dcc5423 --- /dev/null +++ b/app/views/polls/_poll_group.html.erb @@ -0,0 +1,75 @@ +<% poll_group.each do |poll| %> +
    + <% if poll.answerable_by?(current_user) && poll.votable_by?(current_user) %> + <%= link_to poll, + class: "icon-poll-answer can-answer", + title: t("polls.index.can_answer") do %> + + <%= t("polls.index.can_answer") %> + + <% end %> + <% elsif current_user.nil? %> + <%= link_to new_user_session_path, + class: "icon-poll-answer not-logged-in", + title: t("polls.index.cant_answer_not_logged_in") do %> + + <%= t("polls.index.cant_answer_not_logged_in") %> + + <% end %> + <% elsif current_user.unverified? %> + <%= link_to verification_path, + class: "icon-poll-answer unverified", + title: t("polls.index.cant_answer_verify") do %> + + <%= t("polls.index.cant_answer_verify") %> + + <% end %> + <% elsif !poll.votable_by?(current_user) %> +
    "> + <%= t("polls.index.already_answer") %> +
    + <% else %> +
    "> + <%= t("polls.index.cant_answer") %> +
    + <% end %> +
    +
    +
    <%= poll_dates(poll) %>
    + <% if poll.questions.count == 1 %> + <% poll.questions.each do |question| %> +

    <%= link_to question.title, poll %>

    + <% end %> + <% else %> +

    <%= link_to poll.name, poll %>

    +
      + <% poll.questions.each do |question| %> +
    • <%= link_to question.title, question_path(question) %>
    • + <% end %> +
    + <% end %> + <% if poll.geozones.any? %> +

    + <%= t("polls.index.geozone_info") %> +

    + <% end %> +
      + <% poll.geozones.each do |g| %> +
    • <%= g.name %>
    • + <% end %> +
    +
    +
    + <%= link_to poll, class: "button expanded" do %> + <% if poll.expired? %> + <%= t("polls.index.participate_button_expired") %> + <% elsif poll.incoming? %> + <%= t("polls.index.participate_button_incoming") %> + <% else %> + <%= t("polls.index.participate_button") %> + <% end %> + <% end %> +
    +
    +
    +<% end %> diff --git a/app/views/polls/_reasons_for_not_answering.html.erb b/app/views/polls/_reasons_for_not_answering.html.erb new file mode 100644 index 000000000..bd0bab3ad --- /dev/null +++ b/app/views/polls/_reasons_for_not_answering.html.erb @@ -0,0 +1,24 @@ +<% if poll.incoming? %> +
    + <%= t('poll_questions.show.cant_answer_incoming') %> +
    +<% elsif poll.expired? %> +
    + <%= t('poll_questions.show.cant_answer_expired') %> +
    +<% elsif current_user.nil? %> +
    + <%= t("poll_questions.show.not_logged_in", + signin: link_to(t("poll_questions.show.signin"), new_user_session_path, class: "probe-message"), + signup: link_to(t("poll_questions.show.signup"), new_user_registration_path, class: "probe-message")).html_safe %> +
    +<% elsif current_user.unverified? %> +
    + <%= t('poll_questions.show.cant_answer_verify_html', + verify_link: link_to(t('poll_questions.show.verify_link'), verification_path)) %> +
    +<% else %> +
    + <%= t('poll_questions.show.cant_answer_wrong_geozone') %> +
    +<% end %> diff --git a/app/views/polls/index.html.erb b/app/views/polls/index.html.erb new file mode 100644 index 000000000..d8c328267 --- /dev/null +++ b/app/views/polls/index.html.erb @@ -0,0 +1,32 @@ +<% provide :title do %><%= t("polls.index.title") %><% end %> +<% content_for :wrapper_class, "light" %> + +
    +
    +
    +

    <%= t("polls.index.title") %>

    +
    +
    +
    + +
    +
    + <%= render 'shared/filter_subnav_vertical', i18n_namespace: "polls.index" %> +
    + +
    + <% polls_by_geozone_restriction = @polls.group_by(&:geozone_restricted) %> + + <% if polls_by_geozone_restriction[false].present? %> +

    <%= t("polls.index.no_geozone_restricted") %>

    + <%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[false]} %> + <% end %> + + <% if polls_by_geozone_restriction[true].present? %> +

    <%= t("polls.index.geozone_restricted") %>

    + <%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[true]} %> + <% end %> + + <%= paginate @polls %> +
    +
    diff --git a/app/views/polls/questions/_answers.html.erb b/app/views/polls/questions/_answers.html.erb new file mode 100644 index 000000000..c88156b68 --- /dev/null +++ b/app/views/polls/questions/_answers.html.erb @@ -0,0 +1,22 @@ +
    + <% if can? :answer, question %> + <% question.valid_answers.each do |answer| %> + <% if @answers_by_question_id[question.id] == answer %> + "> + <%= answer %> + + <% else %> + <%= link_to answer, + answer_question_path(question, answer: answer), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer), + class: "button secondary hollow" %> + <% end %> + <% end %> + <% else %> + <% question.valid_answers.each do |answer| %> + <%= answer %> + <% end %> + <% end %> +
    diff --git a/app/views/polls/questions/_comments.html.erb b/app/views/polls/questions/_comments.html.erb new file mode 100644 index 000000000..a967406aa --- /dev/null +++ b/app/views/polls/questions/_comments.html.erb @@ -0,0 +1,31 @@ +<% cache [locale_and_user_status, @current_order, commentable_cache_key(@commentable), @comment_tree.comments, @comment_tree.comment_authors, @commentable.comments_count, @comment_flags] do %> +
    +
    +
    +

    + <%= t("shared.comments.title") %> + (<%= @commentable.comments_count %>) +

    + + <%= render 'shared/wide_order_selector', i18n_namespace: "comments" %> + + <% if user_signed_in? %> + <%= render 'comments/form', {commentable: @commentable, parent_id: nil, toggeable: false} %> + <% else %> +
    + +
    + <%= t("shared.comments.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
    + <% end %> + + <% @comment_tree.root_comments.each do |comment| %> + <%= render 'comments/comment', comment: comment %> + <% end %> + <%= paginate @comment_tree.root_comments %> +
    +
    +
    +<% end %> \ No newline at end of file diff --git a/app/views/polls/questions/_question.html.erb b/app/views/polls/questions/_question.html.erb new file mode 100644 index 000000000..f0958b6e4 --- /dev/null +++ b/app/views/polls/questions/_question.html.erb @@ -0,0 +1,9 @@ +
    +

    + <%= link_to question.title, question_path(question) %> +

    + +
    + <%= render 'polls/questions/answers', question: question %> +
    +
    diff --git a/app/views/polls/questions/answer.js.erb b/app/views/polls/questions/answer.js.erb new file mode 100644 index 000000000..aabbd8d89 --- /dev/null +++ b/app/views/polls/questions/answer.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@question) %>_answers").html('<%= j render("polls/questions/answers", question: @question) %>'); diff --git a/app/views/polls/questions/show.html.erb b/app/views/polls/questions/show.html.erb new file mode 100644 index 000000000..2b7225f9f --- /dev/null +++ b/app/views/polls/questions/show.html.erb @@ -0,0 +1,65 @@ +<% provide :title do %><%= @question.title %><% end %> + +
    +
    +
    + <%= render "shared/back_link" %> + +

    <%= @question.title %>

    + + <% if @question.proposal.present? %> +
    + <%= link_to t('poll_questions.show.original_proposal'), @question.proposal %> +
    + <% end %> + + <% if can? :answer, @question %> + <%= link_to t('poll_questions.show.answer_this_question'), + @question.poll, + class: 'large button' %> + <% else %> + <%= render 'polls/reasons_for_not_answering', poll: @question.poll %> + <% end %> +
    + +
    +

    + + <%= t('poll_questions.show.author') %> + +
    + <% if @question.author_visible_name.present? %> + <%= @question.author_visible_name %> + <% else %> + <%= link_to @question.author.name, @question.author %> + <% end %> + +

    + +

    + + <%= t('poll_questions.show.poll') %> + +
    + <%= link_to @question.poll.name, @question.poll %> +

    + +

    + + <%= t('poll_questions.show.dates_title') %> + +
    + <%= poll_dates(@question.poll) %> +

    +
    +
    +
    + +
    +
    +

    <%= t('poll_questions.show.more_info') %>

    + <%= @question.description %> +
    +
    + +<%= render "comments" %> diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb new file mode 100644 index 000000000..8178928b6 --- /dev/null +++ b/app/views/polls/show.html.erb @@ -0,0 +1,53 @@ +<% provide :title do %><%= @poll.name %><% end %> +<% content_for :wrapper_class, "light" %> + +
    +
    +
    +
    + <%= back_link_to polls_path %> + +

    <%= @poll.name %>

    +
      + <% @poll.geozones.each do |g| %> +
    • <%= g.name %>
    • + <% end %> +
    + + <%= render "callout" %> +
    +
    +
    +

    + + <%= t("polls.show.dates_title") %> + +
    + <%= poll_dates(@poll) %> +

    +
    +
    +
    +
    + +
    +
    + <% @questions.each do |question| %> + <%= render 'polls/questions/question', question: question %> + <% end %> +
    + + +
    +
    diff --git a/app/views/proposal_ballots/_successfull_proposal.html.erb b/app/views/proposal_ballots/_successfull_proposal.html.erb deleted file mode 100644 index 870e61605..000000000 --- a/app/views/proposal_ballots/_successfull_proposal.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -
    -

    <%= link_to proposal.title, proposal %>

    -
    - <% if proposal.author.hidden? || proposal.author.erased? %> - <%= t("proposals.show.author_deleted") %> - <% else %> - <%= proposal.author.name %> - <% end %> - -  •  - <%= l proposal.created_at.to_date %> -
    -
    diff --git a/app/views/proposal_ballots/index.html.erb b/app/views/proposal_ballots/index.html.erb deleted file mode 100644 index fe5bdef70..000000000 --- a/app/views/proposal_ballots/index.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -<% provide :title do %><%= t("proposal_ballots.title") %><% end %> -
    -
    -
    -

    - <%= t("proposal_ballots.title") %> -

    -

    - <%= t("proposal_ballots.description_html").html_safe %> -

    -
    - -
    -

    <%= t("proposal_ballots.date_title") %>

    -

    <%= t("proposal_ballots.date") %>

    -
    -
    -
    - -
    -
    -
    - <% if @proposal_ballots.present? %> -
    - <% @proposal_ballots.each do |proposal_for_vote| %> - <%= render "successfull_proposal", proposal: proposal_for_vote %> - <% end %> -
    - <% else %> -

    - <%= t("proposal_ballots.nothing_to_vote") %> -

    - <% end %> -
    -
    -
    diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb index 69bf8d164..735e8bcec 100644 --- a/app/views/proposals/_proposal.html.erb +++ b/app/views/proposals/_proposal.html.erb @@ -1,8 +1,8 @@
    Proposal.votes_needed_for_success) %>" + class="proposal clear <%= ("successful" if proposal.total_votes > Proposal.votes_needed_for_success) %>" data-type="proposal">
    -
    +
    @@ -53,13 +53,21 @@
    - <% if proposal.successfull? %> + <% if proposal.successful? %>
    +

    - <%= t("proposal_ballots.successfull", - voting: link_to(t("proposal_ballots.voting"), proposal_ballots_path)).html_safe %> + <%= t("proposals.proposal.successful", + voting: link_to(t("proposals.proposal.voting"), polls_path)).html_safe %>

    + <% if can? :create, Poll::Question %> +

    + <%= link_to t('poll_questions.create_question'), + new_admin_question_path(proposal_id: proposal.id), + class: "button hollow" %> +

    + <% end %> <% elsif proposal.archived? %>
    <%= t("proposals.proposal.supports", count: proposal.total_votes) %> diff --git a/app/views/proposals/_proposal_ballots_banner.html.erb b/app/views/proposals/_proposal_ballots_banner.html.erb deleted file mode 100644 index df210a535..000000000 --- a/app/views/proposals/_proposal_ballots_banner.html.erb +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/app/views/proposals/_successful_banner.html.erb b/app/views/proposals/_successful_banner.html.erb new file mode 100644 index 000000000..3bde0e097 --- /dev/null +++ b/app/views/proposals/_successful_banner.html.erb @@ -0,0 +1,9 @@ +
    + <%= link_to polls_path do %> +
    +
    +

    <%= t("poll_questions.banner.featured_title") %>

    +

    <%= t("poll_questions.banner.info") %>

    +
    + <% end %> +
    diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index fd781c04b..e04e563e2 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -33,8 +33,8 @@ <%= render "shared/banner" %> <% end %> - <% if @proposal_successfull_exists %> - <%= render "proposal_ballots_banner" %> + <% if @proposal_successful_exists %> + <%= render "successful_banner" %> <% elsif @featured_proposals.present? %>
    NavegadorAcción a realizar NavegadorAcción a realizar
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NombreCreadaÚltima actualizaciónEstado
    + Política de privacidad + + Hace 20 días + + Hace 15 días + + Publicada + + + Ver página + + + Borrar página +
    + FAQ + + Hace 6 días + + Hace 3 días + + + Borrador + + + Ver página + + + Borrar página +
    diff --git a/app/views/sandbox/admin_cms_page.html.erb b/app/views/sandbox/admin_cms_page.html.erb new file mode 100644 index 000000000..ea65176b8 --- /dev/null +++ b/app/views/sandbox/admin_cms_page.html.erb @@ -0,0 +1,61 @@ + + + + Volver + + + + +
    + +
    +

    Editar Política de privacidad

    + +
    + + + + + + + + + +
    + + +
    +
    +
    +
    +
    +

    Opciones

    + + + +
    + +

    Estado

    + + +
    + + +
    + +
    + + +
    + + +
    + +
    +
    + + diff --git a/app/views/shared/_admin_login_items.html.erb b/app/views/shared/_admin_login_items.html.erb index 013320f38..13bbb9d01 100644 --- a/app/views/shared/_admin_login_items.html.erb +++ b/app/views/shared/_admin_login_items.html.erb @@ -11,7 +11,8 @@ <% end %> - <% if feature?(:spending_proposals) && (current_user.administrator? || current_user.valuator?) %> + <% if (feature?(:spending_proposals) || feature?(:budgets)) && + (current_user.administrator? || current_user.valuator?) %>
  • <%= link_to t("layouts.header.valuation"), valuation_root_path %>
  • @@ -22,4 +23,10 @@ <%= link_to t("layouts.header.management"), management_sign_in_path %> <% end %> + + <% if current_user.administrator? || current_user.poll_officer? %> +
  • + <%= link_to t("layouts.header.officing"), officing_root_path %> +
  • + <% end %> <% end %> diff --git a/app/views/shared/_advanced_search.html.erb b/app/views/shared/_advanced_search.html.erb index 7ce6108b4..558adc806 100644 --- a/app/views/shared/_advanced_search.html.erb +++ b/app/views/shared/_advanced_search.html.erb @@ -36,8 +36,6 @@ <%= text_field_tag 'advanced_search[date_min]', params[:advanced_search].try(:[], :date_min), - type: "date", - placeholder: t("shared.advanced_search.date_placeholder"), class: 'js-calendar' %>
    @@ -46,8 +44,6 @@ <%= text_field_tag 'advanced_search[date_max]', params[:advanced_search].try(:[], :date_max), - type: "date", - placeholder: t("shared.advanced_search.date_placeholder"), class: 'js-calendar' %>
    diff --git a/app/views/shared/_filter_subnav_vertical.html.erb b/app/views/shared/_filter_subnav_vertical.html.erb new file mode 100644 index 000000000..b58ecaccc --- /dev/null +++ b/app/views/shared/_filter_subnav_vertical.html.erb @@ -0,0 +1,14 @@ + diff --git a/app/views/shared/_social_media_meta_tags.html.erb b/app/views/shared/_social_media_meta_tags.html.erb index 50a2efec9..c0e6a5e8a 100644 --- a/app/views/shared/_social_media_meta_tags.html.erb +++ b/app/views/shared/_social_media_meta_tags.html.erb @@ -3,7 +3,7 @@ - + <% if setting['url'] %> @@ -14,7 +14,7 @@ <% end %> - + diff --git a/app/views/shared/_subnavigation.html.erb b/app/views/shared/_subnavigation.html.erb index ed5694f27..991d771ce 100644 --- a/app/views/shared/_subnavigation.html.erb +++ b/app/views/shared/_subnavigation.html.erb @@ -5,21 +5,23 @@ <%= layout_menu_link_to t("layouts.header.debates"), debates_path, controller_name == 'debates', - accesskey: "d" %> + accesskey: "1" %> <% end %>
  • <%= layout_menu_link_to t("layouts.header.proposals"), proposals_path, controller_name == 'proposals', - accesskey: "p" %> -
  • -
  • - <%= layout_menu_link_to t("layouts.header.proposal_ballot"), - proposal_ballots_path, - controller_name == 'proposal_ballots', - accesskey: "v" %> + accesskey: "2" %>
  • + <% if feature?(:polls) %> +
  • + <%= layout_menu_link_to t("layouts.header.poll_questions"), + polls_path, + controller_name == "polls", + accesskey: "3" %> +
  • + <% end %> <% if feature?(:spending_proposals) %>
  • <%= layout_menu_link_to t("layouts.header.spending_proposals"), @@ -33,8 +35,14 @@ <%= layout_menu_link_to t("layouts.header.budgets"), budgets_path, controller_name == "budgets" || controller_name == "investments", - accesskey: "b" %> + accesskey: "4" %>
  • <% end %> +
  • + <%= link_to t("layouts.header.more_info"), + more_info_path, + accesskey: "5", + class: ("active" if current_page?(more_info_path)) %> +
  • diff --git a/app/views/shared/_tags.html.erb b/app/views/shared/_tags.html.erb index 3cdb3c40e..ec2b7a1b1 100644 --- a/app/views/shared/_tags.html.erb +++ b/app/views/shared/_tags.html.erb @@ -5,8 +5,7 @@ <% taggable.tag_list_with_limit(limit).each do |tag| %>
  • <%= link_to sanitize(tag.name), - taggables_path(taggable.class.name.underscore, tag.name), - search: tag.name %>
  • + taggables_path(taggable.class.name.underscore, tag.name) %> <% end %> <% if taggable.tags_count_out_of_limit(limit) > 0 %> diff --git a/app/views/shared/_top_links.html.erb b/app/views/shared/_top_links.html.erb index b6b738aca..8a71130d6 100644 --- a/app/views/shared/_top_links.html.erb +++ b/app/views/shared/_top_links.html.erb @@ -1,27 +1,25 @@ diff --git a/app/views/spending_proposals/show.html.erb b/app/views/spending_proposals/show.html.erb index 4cdfb688b..4c5a041d3 100644 --- a/app/views/spending_proposals/show.html.erb +++ b/app/views/spending_proposals/show.html.erb @@ -45,7 +45,7 @@ diff --git a/config/deploy.rb b/config/deploy.rb index 52fa7a255..fa786ee85 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,5 +1,5 @@ # config valid only for current version of Capistrano -lock '3.5.0' +lock '3.8.0' def deploysecret(key) @deploy_secrets_yml ||= YAML.load_file('config/deploy-secrets.yml')[fetch(:stage).to_s] @@ -15,7 +15,6 @@ set :full_app_name, deploysecret(:full_app_name) set :server_name, deploysecret(:server_name) set :repo_url, 'https://github.com/consul/consul.git' -set :scm, :git set :revision, `git rev-parse --short #{fetch(:branch)}`.strip set :log_level, :info @@ -56,4 +55,4 @@ task :install_bundler_gem do on roles(:app) do execute "rvm use #{fetch(:rvm1_ruby_version)}; gem install bundler" end -end \ No newline at end of file +end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 85c391314..ca6eae46e 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -22,6 +22,8 @@ data: ## Another gem (replace %#= with %=): # - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml" - config/locales/%{locale}.yml + - config/locales/activerecord.%{locale}.yml + - config/locales/activemodel.%{locale}.yml - config/locales/admin.%{locale}.yml - config/locales/moderation.%{locale}.yml - config/locales/valuation.%{locale}.yml @@ -32,6 +34,7 @@ data: - config/locales/devise_views.%{locale}.yml - config/locales/responders.%{locale}.yml - config/locales/kaminari.%{locale}.yml + - config/locales/officing.%{locale}.yml - config/locales/budgets.%{locale}.yml # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom: @@ -126,6 +129,8 @@ ignore_unused: - 'admin.activity.show.filter*' - 'admin.comments.index.hidden_*' - 'admin.settings.index.features.*' + - 'admin.polls.*.submit_button' + - 'admin.booths.*.submit_button' - 'moderation.comments.index.filter*' - 'moderation.comments.index.order*' - 'moderation.debates.index.filter*' @@ -137,6 +142,7 @@ ignore_unused: - 'valuation.budgets.index.filter*' - 'valuation.budget_investments.index.filter*' - 'users.show.filters.*' + - 'polls.index.filters.*' - 'debates.index.select_order' - 'debates.index.orders.*' - 'proposals.index.select_order' @@ -150,6 +156,7 @@ ignore_unused: - 'views.pagination.*' # kaminari - 'shared.suggest.*' - 'invisible_captcha.*' + - 'admin.site_customization.pages.page.status_*' # - '{devise,kaminari,will_paginate}.*' # - 'simple_form.{yes,no}' # - 'simple_form.{placeholders,hints,labels}.*' diff --git a/config/initializers/age.rb b/config/initializers/age.rb new file mode 100644 index 000000000..2e90c0a3b --- /dev/null +++ b/config/initializers/age.rb @@ -0,0 +1 @@ +require 'age' diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb index 45c2b70e4..b1d87b01b 100644 --- a/config/initializers/kaminari_config.rb +++ b/config/initializers/kaminari_config.rb @@ -8,50 +8,3 @@ Kaminari.configure do |config| # config.page_method_name = :page # config.param_name = :page end - - -# Overrides for making Kaminari handle i18n pluralization correctly -# -# Remove everything below once https://github.com/amatsuda/kaminari/pull/694 is -# merged in Kaminari and we have updated -module Kaminari - - module ActionViewExtension - def page_entries_info(collection, options = {}) - entry_name = if options[:entry_name] - options[:entry_name].pluralize(collection.size) - else - collection.entry_name(:count => collection.size).downcase - end - - if collection.total_pages < 2 - t('helpers.page_entries_info.one_page.display_entries', entry_name: entry_name, count: collection.total_count) - else - first = collection.offset_value + 1 - last = collection.last_page? ? collection.total_count : collection.offset_value + collection.limit_value - t('helpers.page_entries_info.more_pages.display_entries', entry_name: entry_name, first: first, last: last, total: collection.total_count) - end.html_safe - end - end - - module ActiveRecordRelationMethods - def entry_name(options = {}) - model_name.human(options.reverse_merge(default: model_name.human.pluralize(options[:count]))) - end - end - - module MongoidCriteriaMethods - def entry_name(options = {}) - model_name.human(options.reverse_merge(default: model_name.human.pluralize(options[:count]))) - end - end - - class PaginatableArray < Array - ENTRY = 'entry'.freeze - - def entry_name(options = {}) - I18n.t('helpers.page_entries_info.entry', options.reverse_merge(default: ENTRY.pluralize(options[:count]))) - end - end - -end diff --git a/config/initializers/social_share_button.rb b/config/initializers/social_share_button.rb index d8b6e5254..6ee0fa0ed 100644 --- a/config/initializers/social_share_button.rb +++ b/config/initializers/social_share_button.rb @@ -1,3 +1,3 @@ SocialShareButton.configure do |config| - config.allow_sites = %w(twitter facebook google_plus) + config.allow_sites = %w(twitter facebook google_plus telegram) end diff --git a/config/locales/activemodel.en.yml b/config/locales/activemodel.en.yml index f62fd1ec6..fd378e705 100644 --- a/config/locales/activemodel.en.yml +++ b/config/locales/activemodel.en.yml @@ -15,4 +15,8 @@ en: phone: "Telephone" confirmation_code: "Confirmation code" email: - recipient: "Email" \ No newline at end of file + recipient: "Email" + officing/residence: + document_type: "Document type" + document_number: "Document number (including letters)" + year_of_birth: "Year born" \ No newline at end of file diff --git a/config/locales/activemodel.es.yml b/config/locales/activemodel.es.yml index e45eade16..dafc45a87 100644 --- a/config/locales/activemodel.es.yml +++ b/config/locales/activemodel.es.yml @@ -15,4 +15,8 @@ es: phone: "Teléfono" confirmation_code: "Código de confirmación" email: - recipient: "Email" \ No newline at end of file + recipient: "Email" + officing/residence: + document_type: "Tipo documento" + document_number: "Numero de documento (incluida letra)" + year_of_birth: "Año de nacimiento" diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 04c5cc9d0..608803579 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -34,12 +34,27 @@ en: organization: one: "Organisation" other: "Organisations" + poll/booth: + one: "booth" + other: "booths" + poll/officer: + one: "officer" + other: "officers" proposal: one: "Citizen proposal" other: "Citizen proposals" spending_proposal: one: "Spending proposal" other: "Spending proposals" + site_customization/page: + one: Custom page + other: Custom pages + site_customization/image: + one: Custom image + other: Custom images + site_customization/content_block: + one: Custom content block + other: Custom content blocks attributes: budget: name: "Name" @@ -99,10 +114,38 @@ en: external_url: "Link to additional documentation" geozone_id: "Scope of operation" title: "Title" + poll: + name: "Name" + starts_at: "Start Date" + ends_at: "Closing Date" + geozone_restricted: "Restricted by geozone" + poll/question: + title: "Question" + valid_answers: "Posibles answers" + summary: "Summary" + description: "Description" + external_url: "Link to additional documentation" signature_sheet: signable_type: "Signable type" signable_id: "Signable ID" document_numbers: "Documents numbers" + site_customization/page: + content: Content + created_at: Created at + subtitle: Subtitle + slug: Slug + status: Status + title: Title + updated_at: Updated at + more_info_flag: Show in more information page + print_content_flag: Print content button + site_customization/image: + name: Name + image: Image + site_customization/content_block: + name: Name + locale: locale + body: Body errors: models: user: @@ -117,6 +160,11 @@ en: attributes: max_per_day: invalid: "You have reached the maximum number of private messages per day" + poll/voter: + attributes: + document_number: + not_in_census: "Document not in census" + has_voted: "User has already voted" proposal: attributes: tag_list: @@ -134,3 +182,12 @@ en: document_number: not_in_census: 'Not verified by Census' already_voted: 'Already voted this proposal' + site_customization/page: + attributes: + slug: + slug_format: "must be letters, numbers, _ and -" + site_customization/image: + attributes: + image: + image_width: "Width must be %{required_width}px" + image_height: "Height must be %{required_height}px" diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index c71a34178..71e166026 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -8,8 +8,8 @@ es: one: "Presupuesto participativo" other: "Presupuestos participativos" budget/investment: - one: "Propuesta de inversión" - other: "Propuestas de inversión" + one: "Proyecto de inversión" + other: "Proyectos de inversión" comment: one: "Comentario" other: "Comentarios" @@ -37,19 +37,34 @@ es: proposal: one: "Propuesta ciudadana" other: "Propuestas ciudadanas" + poll/booth: + one: "urna" + other: "urnas" + poll/officer: + one: "presidente de mesa" + other: "presidentes de mesa" spending_proposal: one: "Propuesta de inversión" other: "Propuestas de inversión" + site_customization/page: + one: Página + other: Páginas + site_customization/image: + one: Imagen + other: Imágenes + site_customization/content_block: + one: Bloque + other: Bloques attributes: budget: name: "Nombre" - description_accepting: "Descripción durante la fase de aceptación" - description_reviewing: "Descripción durante la fase de revisión" - description_selecting: "Descripción durante la fase de selección" + description_accepting: "Descripción durante la fase de presentación de proyectos" + description_reviewing: "Descripción durante la fase de revisión interna" + description_selecting: "Descripción durante la fase de apoyos" description_valuating: "Descripción durante la fase de evaluación" description_balloting: "Descripción durante la fase de votación" description_reviewing_ballots: "Descripción durante la fase de revisión de votos" - description_finished: "Descripción cuando el presupuesto ha finalizado" + description_finished: "Descripción cuando el presupuesto ha finalizado / Resultados" phase: "Fase" currency_symbol: "Divisa" budget/investment: @@ -94,10 +109,38 @@ es: external_url: "Enlace a documentación adicional" geozone_id: "Ámbito de actuación" title: "Título" + poll: + name: "Nombre" + starts_at: "Fecha de apertura" + ends_at: "Fecha de cierre" + geozone_restricted: "Restringida por zonas" + poll/question: + title: "Pregunta" + valid_answers: "Posibles respuestas" + summary: "Resumen" + description: "Descripción" + external_url: "Enlace a documentación adicional" signature_sheet: signable_type: "Tipo de hoja de firmas" signable_id: "ID Propuesta ciudadana/Propuesta inversión" document_numbers: "Números de documentos" + site_customization/page: + content: Contenido + created_at: Creada + subtitle: Subtítulo + slug: Slug + status: Estado + title: Título + updated_at: última actualización + more_info_flag: Mostrar en la página de más información + print_content_flag: Botón de imprimir contenido + site_customization/image: + name: Nombre + image: Imagen + site_customization/content_block: + name: Nombre + locale: Idioma + body: Contenido errors: models: user: @@ -112,10 +155,19 @@ es: attributes: max_per_day: invalid: "Has llegado al número máximo de mensajes privados por día" + poll/voter: + attributes: + document_number: + not_in_census: "Este documento no aparece en el censo" + has_voted: "Este usuario ya ha votado" proposal: attributes: tag_list: less_than_or_equal_to: "los temas deben ser menor o igual que %{count}" + budget/investment: + attributes: + tag_list: + less_than_or_equal_to: "los temas deben ser menor o igual que %{count}" proposal_notification: attributes: minimum_interval: @@ -125,3 +177,12 @@ es: document_number: not_in_census: 'No verificado por Padrón' already_voted: 'Ya ha votado esta propuesta' + site_customization/page: + attributes: + slug: + slug_format: "deber ser letras, números, _ y -" + site_customization/image: + attributes: + image: + image_width: "Debe tener %{required_width}px de ancho" + image_height: "Debe tener %{required_height}px de alto" diff --git a/config/locales/admin.en.yml b/config/locales/admin.en.yml index 460319748..5ae6d6f49 100755 --- a/config/locales/admin.en.yml +++ b/config/locales/admin.en.yml @@ -1,7 +1,10 @@ --- en: admin: + header: + title: Administration actions: + actions: Actions confirm: Are you sure? confirm_hide: Confirm hide: Hide @@ -9,6 +12,8 @@ en: restore: Restore mark_featured: Featured unmark_featured: Unmark featured + edit: Edit + configure: Configure banners: index: title: Banners @@ -190,23 +195,37 @@ en: activity: Moderator activity admin: Admin menu banner: Manage banners + poll_questions: Poll questions + proposals_topics: Proposals topics budgets: Participatory budgets - debate_topics: Debate topics geozones: Manage geozones hidden_comments: Hidden comments hidden_debates: Hidden debates hidden_proposals: Hidden proposals hidden_users: Hidden users - incomplete_verifications: Incomplete verifications managers: Managers moderators: Moderators valuators: Valuators + poll_officers: Poll officers + polls: Polls + poll_booths: Booths location officials: Officials organizations: Organisations settings: Configuration settings spending_proposals: Spending proposals stats: Statistics signature_sheets: Signature Sheets + site_customization: + pages: Custom Pages + images: Custom Images + content_blocks: Custom content blocks + title_categories: Categories + title_moderated_content: Moderated content + title_budgets: Budgets + title_polls: Polls + title_profiles: Profiles + title_banners: Banners + title_site_customization: Site customization moderators: index: title: Moderators @@ -237,6 +256,162 @@ en: in_evaluation_count: In evaluation total_count: Total cost: Cost + poll_officers: + index: + title: Poll officers + officer: + add: Add + delete: Delete position + name: Name + email: Email + entry_name: officer + search: + email_placeholder: Search user by email + search: Search + user_not_found: User not found + poll_officer_assignments: + flash: + destroy: "Officing shift removed" + create: "Officing shift added" + error_destroy: "An error ocurred when removing officer assignment" + error_create: "An error ocurred when adding officer assignment" + index: + officers_title: "List of officers" + no_officers: "There are no officers assigned to this poll." + table_name: "Name" + table_email: "Email" + add_officer_assignments: "Add shifts as officer" + edit_officer_assignments: "Edit officing shifts" + by_officer: + new_assignment: "New shift" + date: "Date" + booth: "Booth" + assignment: "Assignment" + select_date: "Select day" + select_booth: "Select booth" + add_assignment: "Add shift" + remove_assignment: "Remove" + assignments: "Officing shifts in this poll" + no_assignments: "This user has no officing shifts in this poll." + recounts: "Daily recounts" + recount: "Daily recount (by officer)" + final_recounts: "Final recounts" + final_recount: "Final recount (by officer)" + poll_booth_assignments: + flash: + destroy: "Booth not assigned anymore" + create: "Booth assigned" + error_destroy: "An error ocurred when removing booth assignment" + error_create: "An error ocurred when assigning booth to the poll" + show: + location: "Location" + officers: "Officers" + officers_list: "Officer list for this booth" + no_officers: "There are no officers for this booth" + recounts: "Recounts" + recounts_list: "Recount list for this booth" + no_recounts: "There are not daily recounts of this booth yet" + date: "Date" + count_by_officer: "Daily recount (by officer)" + count_final: "Final recount (by officer)" + count_by_system: "Votes (automatic)" + index: + booths_title: "List of booths" + no_booths: "There are no booths assigned to this poll." + table_name: "Name" + table_location: "Location" + table_assignment: "Assignment" + remove_booth: "Remove booth from poll" + add_booth: "Assign booth" + polls: + index: + title: "List of polls" + no_polls: "There are no polls." + create: "Create poll" + name: "Name" + dates: "Dates" + new: + title: "New poll" + submit_button: "Create poll" + edit: + title: "Edit poll" + submit_button: "Update poll" + show: + questions_tab: Questions + booths_tab: Booths + officers_tab: Officers + recounts_tab: Recounting + results_tab: Results + no_questions: "There are no questions assigned to this poll." + questions_title: "List of questions" + remove_question: "Remove question from poll" + add_question: "Include question" + table_title: "Title" + table_assignment: "Assignment" + table_name: "Name" + flash: + question_added: "Question added to this poll" + error_on_question_added: "Question could not be assigned to this poll" + question_removed: "Question removed from this poll" + error_on_question_removed: "Question could not be removed from this poll" + questions: + index: + title: "Questions" + create: "Create question" + no_questions: "There are no questions." + filter_poll: Filter by Poll + select_poll: Select Poll + questions_tab: "Questions" + successful_proposals_tab: "Successful proposals" + create_question: "Create question" + table_proposal: "Proposal" + table_question: "Question" + edit: + title: "Edit Question" + new: + title: "Create Question" + poll_label: "Poll" + valid_answers_note: "Enter the answers separated by commas (,)" + show: + proposal: Original proposal + author: Author + title: Title + valid_answers: Valid answers + description: Description + preview: View on website + recounts: + index: + title: "Recounts" + no_recounts: "There is nothing to be recounted" + table_booth_name: "Booth" + table_recounts: "Accumulated daily recounts (by officer)" + table_final_recount: "Final recount (by officer)" + table_system_count: "Votes (automatic)" + results: + index: + title: "Results" + no_results: "There are no results" + table_whites: "Blank ballots" + table_nulls: "Invalid ballots" + table_answer: Answer + table_votes: Votes + booths: + index: + title: "List of booths" + no_booths: "There are no booths." + add_booth: "Add booth" + name: "Name" + location: "Location" + new: + title: "New booth" + name: "Name" + location: "Location" + submit_button: "Create booth" + edit: + title: "Edit booth" + submit_button: "Update booth" + show: + location: "Location" officials: edit: destroy: Remove 'Official' status @@ -299,6 +474,15 @@ en: enable: "Enable" disable: "Disable" shared: + booths_search: + button: Search + placeholder: Search booth by name + poll_officers_search: + button: Search + placeholder: Search poll officers + poll_questions_search: + button: Search + placeholder: Search poll questions proposal_search: button: Search placeholder: Search proposals by title, code, description or question @@ -308,6 +492,8 @@ en: user_search: button: Search placeholder: Search user by name or email' + search_results: "Search results" + no_search_results: "No results found." spending_proposals: index: geozone_filter_all: All zones @@ -439,6 +625,7 @@ en: visits_title: Visits direct_messages: Direct messages proposal_notifications: Proposal notifications + incomplete_verifications: Incomplete verifications direct_messages: title: Direct messages total: Total @@ -451,9 +638,9 @@ en: create: Create Topic destroy: Destroy Topic index: - add_tag: Add a new debate topic - title: Debate topics - mark_as_featured: Propose topic upon creating debate + add_tag: Add a new proposal topic + title: Proposal topics + mark_as_featured: Propose topic upon creating proposal name: placeholder: Type the name of the topic update: Update Topic @@ -475,3 +662,77 @@ en: phone_not_given: Phone not given sms_code_not_confirmed: Has not confirmed the sms code title: Incomplete verifications + site_customization: + content_blocks: + form: + content_blocks_information: Information about content blocks + content_block_about: You can create HTML content blocks to be inserted in the header or the footer of your Consul. + content_block_top_links_html: "Header blocks (top_links) are blocks of links that must have this format:" + content_block_footer_html: "Footer blocks can have any format and can be used to insert Javascript, CSS or custom HTML." + create: + notice: Content block created successfully + error: Content block couldn't be created + update: + notice: Content block updated successfully + error: Content block couldn't be updated + destroy: + notice: Content block deleted successfully + edit: + title: Editing content block + back: Back + errors: + form: + error: Error + index: + create: Create new content block + delete: Delete + title: Content blocks + new: + back: Back + title: Create new content block + content_block: + body: Body + name: Name + images: + index: + title: Custom images + update: Update + delete: Delete + update: + notice: Image updated successfully + error: Image couldn't be updated + destroy: + notice: Image deleted successfully + error: Image couldn't be deleted + pages: + create: + notice: Page created successfully + error: Page couldn't be created + update: + notice: Page updated successfully + error: Page couldn't be updated + destroy: + notice: Page deleted successfully + edit: + title: Editing %{page_title} + back: Back + errors: + form: + error: Error + form: + options: Options + index: + create: Create new page + delete: Delete + title: Custom Pages + see_page: See page + new: + back: Back + title: Create new custom page + page: + created_at: Created at + status: Status + title: Title + updated_at: Updated at + status_draft: Draft + status_published: Published diff --git a/config/locales/admin.es.yml b/config/locales/admin.es.yml index 7d6438416..f5db64135 100644 --- a/config/locales/admin.es.yml +++ b/config/locales/admin.es.yml @@ -1,7 +1,10 @@ --- es: admin: + header: + title: Administración actions: + actions: Acciones confirm: "¿Estás seguro?" confirm_hide: Confirmar hide: Ocultar @@ -9,6 +12,8 @@ es: restore: Volver a mostrar mark_featured: Destacar unmark_featured: Quitar destacado + edit: Editar + configure: Configurar banners: index: title: Banners @@ -190,23 +195,37 @@ es: activity: Actividad de moderadores admin: Menú de administración banner: Gestionar banners + proposals_topics: Temas de propuestas + poll_questions: Preguntas ciudadanas budgets: Presupuestos participativos - debate_topics: Temas de debate geozones: Gestionar distritos hidden_comments: Comentarios ocultos hidden_debates: Debates ocultos hidden_proposals: Propuestas ocultas hidden_users: Usuarios bloqueados - incomplete_verifications: Verificaciones incompletas managers: Gestores moderators: Moderadores valuators: Evaluadores + poll_officers: Presidentes de mesa + polls: Votaciones + poll_booths: Ubicación de urnas officials: Cargos públicos organizations: Organizaciones settings: Configuración global spending_proposals: Propuestas de inversión stats: Estadísticas signature_sheets: Hojas de firmas + site_customization: + pages: Personalizar páginas + images: Personalizar imágenes + content_blocks: Personalizar bloques + title_categories: Categorías + title_moderated_content: Contenido moderado + title_budgets: Presupuestos + title_polls: Votaciones + title_profiles: Perfiles + title_banners: Banners + title_site_customization: Personalizar sitio moderators: index: title: Moderadores @@ -237,6 +256,162 @@ es: in_evaluation_count: En evaluación total_count: Total cost: Coste total + poll_officers: + index: + title: Presidentes de mesa + officer: + add: Añadir como Presidente de mesa + delete: Eliminar cargo + name: Nombre + email: Email + entry_name: presidente de mesa + search: + email_placeholder: Buscar usuario por email + search: Buscar + user_not_found: Usuario no encontrado + poll_officer_assignments: + flash: + destroy: "Eliminado turno de presidente de mesa" + create: "Añadido turno de presidente de mesa" + error_destroy: "Se ha producido un error al eliminar el turno" + error_create: "Se ha producido un error al intentar crear el turno" + index: + officers_title: "Listado de presidentes de mesa asignados" + no_officers: "No hay presidentes de mesa asignados a esta votación." + table_name: "Nombre" + table_email: "Email" + add_officer_assignments: "Añadir turnos como presidente de mesa" + edit_officer_assignments: "Editar turnos" + by_officer: + new_assignment: "Nuevo turno" + date: "Fecha" + booth: "Urna" + assignment: "Asignación" + select_date: "Seleccionar día" + select_booth: "Seleccionar urna" + add_assignment: "Añadir turno" + remove_assignment: "Eliminar turno" + assignments: "Turnos como presidente de mesa en esta votación" + no_assignments: "No tiene turnos como presidente de mesa en esta votación." + recounts: "Recuentos diarios" + recount: "Recuento diario (presidente de mesa)" + final_recounts: "Recuentos finales" + final_recount: "Recuento final (presidente de mesa)" + poll_booth_assignments: + flash: + destroy: "Urna desasignada" + create: "Urna asignada" + error_destroy: "Se ha producido un error al desasignar la urna" + error_create: "Se ha producido un error al intentar asignar la urna" + show: + location: "Ubicación" + officers: "Presidentes de mesa" + officers_list: "Lista de presidentes de mesa asignados a esta urna" + no_officers: "No hay presidentes de mesa para esta urna" + recounts: "Recuentos" + recounts_list: "Lista de recuentos de esta urna" + no_recounts: "No hay recuentos diarios de esta urna" + date: "Fecha" + count_by_officer: "Recuento diario (presidente de mesa)" + count_final: "Recuento final (presidente de mesa)" + count_by_system: "Votos (automático)" + index: + booths_title: "Listado de urnas asignadas" + no_booths: "No hay urnas asignadas a esta votación." + table_name: "Nombre" + table_location: "Ubicación" + table_assignment: "Asignación" + remove_booth: "Desasignar urna" + add_booth: "Asignar urna" + polls: + index: + title: "Listado de votaciones" + no_polls: "No hay ninguna votación." + create: "Crear votación" + name: "Nombre" + dates: "Fechas" + new: + title: "Nueva votación" + submit_button: "Crear votación" + edit: + title: "Editar votación" + submit_button: "Actualizar votación" + show: + questions_tab: Preguntas + booths_tab: Urnas + officers_tab: Presidentes de mesa + recounts_tab: Recuentos + results_tab: Resultados + no_questions: "No hay preguntas asignadas a esta votación." + questions_title: "Listado de preguntas asignadas" + remove_question: "Desasignar pregunta" + add_question: "Incluir pregunta" + table_title: "Título" + table_assignment: "Asignación" + table_name: "Nombre" + flash: + question_added: "Pregunta añadida a esta votación" + error_on_question_added: "No se pudo asignar la pregunta" + question_removed: "Pregunta eliminada de esta votación" + error_on_question_removed: "No se pudo quitar la pregunta" + questions: + index: + title: "Preguntas ciudadanas" + create: "Crear pregunta ciudadana" + no_questions: "No hay ninguna pregunta ciudadana." + filter_poll: "Filtrar por votación" + select_poll: "Seleccionar votación" + questions_tab: "Preguntas ciudadanas" + successful_proposals_tab: "Propuestas que han superado el umbral" + create_question: "Crear pregunta para votación" + table_proposal: "Propuesta" + table_question: "Pregunta" + edit: + title: "Editar pregunta ciudadana" + new: + title: "Crear pregunta ciudadana" + poll_label: "Votación" + valid_answers_note: "Escribe las respuestas separadas por comas (,)" + show: + proposal: Propuesta ciudadana original + author: Autor + title: Título + valid_answers: Respuestas válidas + description: Descripción + preview: Ver en la web + recounts: + index: + title: "Recuentos" + no_recounts: "No hay nada de lo que hacer recuento" + table_booth_name: "Urna" + table_recounts: "Recuentos diarios acumulados (presidente de mesa)" + table_final_recount: "Recuento final (presidente de mesa)" + table_system_count: "Votos (automático)" + results: + index: + title: "Resultados" + no_results: "No hay resultados" + table_whites: Papeletas en blanco + table_nulls: Papeletas nulas + table_answer: Respuesta + table_votes: Votos + booths: + index: + title: "Lista de urnas" + no_booths: "No hay urnas." + add_booth: "Añadir urna" + name: "Nombre" + location: "Ubicación" + new: + title: "Nueva urna" + name: "Nombre" + location: "Ubicación" + submit_button: "Crear urna" + edit: + title: "Editar urna" + submit_button: "Actualizar urna" + show: + location: "Ubicación" officials: edit: destroy: Eliminar condición de 'Cargo Público' @@ -299,6 +474,15 @@ es: enable: "Activar" disable: "Desactivar" shared: + booths_search: + button: Buscar + placeholder: Buscar urna por nombre + poll_officers_search: + button: Buscar + placeholder: Buscar presidentes de mesa + poll_questions_search: + button: Buscar + placeholder: Buscar preguntas proposal_search: button: Buscar placeholder: Buscar propuestas por título, código, descripción o pregunta @@ -308,6 +492,8 @@ es: user_search: button: Buscar placeholder: Buscar usuario por nombre o email + search_results: "Resultados de la búsqueda" + no_search_results: "No se han encontrado resultados." spending_proposals: index: geozone_filter_all: Todos los ámbitos de actuación @@ -439,6 +625,7 @@ es: visits_title: Visitas direct_messages: Mensajes directos proposal_notifications: Notificaciones de propuestas + incomplete_verifications: Verificaciones incompletas direct_messages: title: Mensajes directos total: Total @@ -451,9 +638,9 @@ es: create: Crear Tema destroy: Eliminar Tema index: - add_tag: Añade un nuevo tema de debate - title: Temas de debate - mark_as_featured: Proponer tema al crear debate + add_tag: Añade un nuevo tema de propuesta + title: Temas de propuesta + mark_as_featured: Proponer tema al crear propuesta name: placeholder: Escribe el nombre del tema update: Actualizar Tema @@ -475,3 +662,77 @@ es: phone_not_given: No ha dado su teléfono sms_code_not_confirmed: No ha introducido su código de seguridad title: Verificaciones incompletas + site_customization: + content_blocks: + form: + content_blocks_information: Información sobre los bloques de texto + content_block_about: Puedes crear bloques de HTML que se incrustarán en la cabecera o el pie de tu Cónsul. + content_block_top_links_html: "Los bloques de la cabecera (top_links) son bloques de enlaces que deben crearse con este formato:" + content_block_footer_html: "Los bloques del pie (footer) pueden tener cualquier formato y se pueden utilizar para guardar huellas Javascript, contenido CSS o contenido HTML personalizado." + create: + notice: Bloque creado correctamente + error: No se ha podido crear el bloque + update: + notice: Bloque actualizado correctamente + error: No se ha podido actualizar el bloque + destroy: + notice: Bloque borrado correctamente + edit: + title: Editar bloque + back: Volver + errors: + form: + error: Error + index: + create: Crear nuevo bloque + delete: Borrar + title: Bloques + new: + back: Volver + title: Crear nuevo bloque + content_block: + body: Contenido + name: Nombre + images: + index: + title: Personalizar imágenes + update: Actualizar + delete: Borrar + update: + notice: Imagen actualizada correctamente + error: No se ha podido actualizar la imagen + destroy: + notice: Imagen borrada correctamente + error: No se ha podido borrar la imagen + pages: + create: + notice: Página creada correctamente + error: No se ha podido crear la página + update: + notice: Página actualizada correctamente + error: No se ha podido actualizar la página + destroy: + notice: Página eliminada correctamente + edit: + title: Editar %{page_title} + back: Volver + errors: + form: + error: Error + form: + options: Opciones + index: + create: Crear nueva página + delete: Borrar + title: Páginas + see_page: Ver página + new: + back: Back + title: Página nueva + page: + created_at: Creada + status: Estado + title: Título + updated_at: Última actualización + status_draft: Borrador + status_published: Publicada diff --git a/config/locales/budgets.en.yml b/config/locales/budgets.en.yml index 5e4861a16..7b3251ad4 100644 --- a/config/locales/budgets.en.yml +++ b/config/locales/budgets.en.yml @@ -25,11 +25,11 @@ en: unfeasible_title: Unfeasible investments unfeasible: See unfeasible investments phase: - accepting: Accepting proposals - reviewing: Reviewing proposals - selecting: Selecting proposals - valuating: Valuating proposals - balloting: Balloting proposals + accepting: Accepting projects + reviewing: Reviewing projects + selecting: Selecting projects + valuating: Valuating projects + balloting: Balloting projects reviewing_ballots: Reviewing Ballots finished: Finished budget index: diff --git a/config/locales/budgets.es.yml b/config/locales/budgets.es.yml index 08a33bde8..44b748698 100644 --- a/config/locales/budgets.es.yml +++ b/config/locales/budgets.es.yml @@ -25,13 +25,13 @@ es: unfeasible_title: Propuestas inviables unfeasible: Ver propuestas inviables phase: - accepting: Aceptando propuestas - reviewing: Revisando propuestas - selecting: Selección de propuestas - valuating: Evaluación de propuestas - balloting: Votación de propuestas - reviewing_ballots: Contando resultados - finished: Presupuesto terminado + accepting: Presentación de proyectos + reviewing: Revisión interna de proyectos + selecting: Fase de apoyos + valuating: Evaluación de proyectos + balloting: Votación final + reviewing_ballots: Votación finalizada + finished: Resultados index: title: Presupuestos participativos investments: diff --git a/config/locales/en.yml b/config/locales/en.yml index 7d5b5a8f2..b6001a744 100755 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -139,6 +139,7 @@ en: errors: messages: user_not_found: User not found + invalid_date_range: "Invalid date range" form: accept_terms: I agree to the %{policy} and the %{conditions} accept_terms_title: I agree to the Privacy Policy and the Terms and conditions of use @@ -174,7 +175,6 @@ en: copyright: Consul, %{year} description: This portal uses the %{consul} which is %{open_source}. faq: technical assistance - more_info: More information open_data_text: Every detail about the City Council is yours to access. open_data_title: Open data open_source: open-source software @@ -191,7 +191,7 @@ en: debates: Debates external_link_blog: Blog external_link_opendata: Open data - external_link_opendata_url: "/opendata" + external_link_opendata_url: https://opendata.consul external_link_transparency: Transparency external_link_transparency_url: https://transparency.consul locale: 'Language:' @@ -199,7 +199,8 @@ en: management: Management moderation: Moderation valuation: Valuation - more_information: More information + officing: Polling officers + more_info: More information my_account_link: My account my_activity_link: My activity notifications: Notifications @@ -212,7 +213,7 @@ en: open_city_title: Love the city, and it will become a city you love. open_gov: Open government proposals: Proposals - proposal_ballot: Voting + poll_questions: Voting see_all: See proposals budgets: Participatory budgeting spending_proposals: Spending Proposals @@ -366,6 +367,8 @@ en: supports_necessary: "%{number} supports needed" total_percent: 100% archived: "This proposal has been archived and can't collect supports." + successful: "This proposal has reached the required supports and will be voted in the %{voting}." + voting: "next voting" show: author_deleted: User deleted code: 'Proposal code:' @@ -384,20 +387,64 @@ en: share: Share send_notification: Send notification no_notifications: "This proposal has any notifications." + embed_video_title: "Video on %{proposal}" update: form: submit_button: Save changes - proposal_ballots: - title: "Votings" - description_html: "The following citizen proposals that have reached the required supports and will be voted." - date_title: "Dates of participation" - date: "Soon we'll announce the date of vote these proposals." - successfull: "This proposal has reached the required supports and will be voted in the %{voting}." - voting: "next voting" - featured_title: "#NextVoting" - nothing_to_vote: "There is nothing to vote at the moment." - info: "New proposals that have reached the voting phase." - button: "I want to decide" + polls: + all: "All" + no_dates: "no date assigned" + dates: "From %{open_at} to %{closed_at}" + final_date: "Final recounts/Results" + index: + filters: + current: "Open" + incoming: "Incoming" + expired: "Expired" + title: "Polls" + participate_button: "Participate in this poll" + participate_button_incoming: "More information" + participate_button_expired: "Poll ended" + no_geozone_restricted: "All city" + geozone_restricted: "Districts" + geozone_info: "Can participate people in the Census of: " + can_answer: "You can participate in this poll!" + cant_answer: "This poll is not available on your geozone" + cant_answer_not_logged_in: "You must sign in or sign up to participate" + cant_answer_verify: "You must verify your account in order to answer" + already_answer: "You already have participated in this poll" + show: + dates_title: "Participation dates" + cant_answer_not_logged_in: "You must %{signin} or %{signup} to participate." + signin: Sign in + signup: Sign up + cant_answer_verify_html: "You must %{verify_link} in order to answer." + verify_link: "verify your account" + cant_answer_incoming: "This poll has not yet started." + cant_answer_expired: "This poll has finished." + poll_questions: + create_question: "Create question" + default_valid_answers: "Yes, No" + show: + answer_this_question: "Answer this question" + original_proposal: "Original proposal" + author: "Created by" + dates_title: "Participation dates" + more_info: "More information" + not_logged_in: "You must %{signin} or %{signup} to participate." + signin: Sign in + signup: Sign up + cant_answer_verify_html: "You must %{verify_link} in order to answer." + verify_link: "verify your account" + cant_answer_incoming: "This poll has not yet started." + cant_answer_expired: "This poll has finished." + cant_answer_wrong_geozone: "This question is not available on your geozone." + vote_answer: "Vote %{answer}" + voted: "You have voted %{answer}" + poll: "Poll" + banner: + featured_title: "#NextVoting" + info: "New proposals that have reached the voting phase." proposal_notifications: new: title: "Send message" @@ -409,6 +456,12 @@ en: show: back: "Go back to my activity" shared: + edit: 'Edit' + save: 'Save' + delete: 'Delete' + comments: + title: 'Comments' + login_to_comment: 'You must %{signin} or %{signup} to leave a comment.' "yes": "Yes" "no": "No" advanced_search: @@ -474,6 +527,7 @@ en: twitter: Twitter youtube: YouTube whatsapp: WhatsApp + telegram: Telegram spending_proposals: form: association_name_label: 'If you propose in name of an assocation or collective add the name here' diff --git a/config/locales/es.yml b/config/locales/es.yml index 495a940a3..7d942a1e6 100755 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -139,6 +139,7 @@ es: errors: messages: user_not_found: No se encontró el usuario + invalid_date_range: "El rango de fechas no es válido" form: accept_terms: Acepto la %{policy} y las %{conditions} accept_terms_title: Acepto la Política de privacidad y las Condiciones de uso @@ -174,7 +175,6 @@ es: copyright: Consul, %{year} description: Este portal usa la %{consul} que es %{open_source}. faq: Asistencia técnica - more_info: Más información open_data_text: Todos los datos del Ayuntamiento son tuyos. open_data_title: Datos Abiertos open_source: software libre @@ -191,7 +191,7 @@ es: debates: Debates external_link_blog: Blog external_link_opendata: Datos abiertos - external_link_opendata_url: "/opendata" + external_link_opendata_url: https://opendata.consul external_link_transparency: Transparencia external_link_transparency_url: https://transparency.consul locale: 'Idioma:' @@ -199,7 +199,8 @@ es: management: Gestión moderation: Moderar valuation: Evaluación - more_information: Más información + officing: Presidentes de mesa + more_info: Más información my_account_link: Mi cuenta my_activity_link: Mi actividad notifications: Notificaciones @@ -212,7 +213,7 @@ es: open_city_title: La ciudad que quieres será la ciudad que quieras. open_gov: Gobierno %{open} proposals: Propuestas - proposal_ballot: Votaciones + poll_questions: Votaciones see_all: Ver propuestas budgets: Presupuestos ciudadanos spending_proposals: "Propuestas de inversión" @@ -366,6 +367,8 @@ es: supports_necessary: "%{number} apoyos necesarios" total_percent: 100% archived: "Esta propuesta ha sido archivada y ya no puede recoger apoyos." + successful: "Esta propuesta ha alcanzado los apoyos necesarios y pasará a la %{voting}." + voting: "próxima votación" show: author_deleted: Usuario eliminado code: 'Código de la propuesta:' @@ -384,20 +387,64 @@ es: send_notification: Enviar notificación share: Compartir no_notifications: "Esta propuesta no tiene notificaciones." + embed_video_title: "Vídeo en %{proposal}" update: form: submit_button: Guardar cambios - proposal_ballots: - title: "Votaciones" - description_html: "Las siguientes propuestas ciudadanas han alcanzado el número de apoyos necesarios y pasarán a votación." - date_title: "Fechas de participación" - date: "En breve anunciaremos la fecha de votación de estas propuestas." - successfull: "Esta propuesta ha alcanzado los apoyos necesarios y pasará a la %{voting}." - voting: "próxima votación" - featured_title: "#PróximaVotación" - nothing_to_vote: "No hay nada que votar en este momento." - info: "Nuevas propuestas ciudadanas han llegado a la fase de votación." - button: "Quiero decidir" + polls: + all: "Todas" + no_dates: "sin fecha asignada" + dates: "Desde el %{open_at} hasta el %{closed_at}" + final_date: "Recuento final/Resultados" + index: + filters: + current: "Abiertas" + incoming: "Próximamente" + expired: "Terminadas" + title: "Votaciones" + participate_button: "Participar en esta votación" + participate_button_incoming: "Más información" + participate_button_expired: "Votación terminada" + no_geozone_restricted: "Toda la ciudad" + geozone_restricted: "Distritos" + geozone_info: "Pueden participar las personas empadronadas en: " + can_answer: "¡Puedes participar en esta votación!" + cant_answer: "Esta votación no está disponible en tu zona" + cant_answer_not_logged_in: "Necesitas iniciar sesión o registrarte para participar" + cant_answer_verify: "Por favor verifica tu cuenta para poder responder" + already_answer: "Ya has participado en esta votación" + show: + dates_title: "Fechas de participación" + cant_answer_not_logged_in: "Necesitas %{signin} o %{signup} para participar." + signin: iniciar sesión + signup: registrarte + cant_answer_verify_html: "Por favor %{verify_link} para poder responder." + verify_link: "verifica tu cuenta" + cant_answer_incoming: "Esta votación todavía no ha comenzado." + cant_answer_expired: "Esta votación ha terminado." + poll_questions: + create_question: "Crear pregunta para votación" + default_valid_answers: "Sí, No" + show: + answer_this_question: "Responder a esta pregunta" + original_proposal: "Propuesta original" + author: "Creado por" + dates_title: "Fechas de participación" + more_info: "Más información" + not_logged_in: "Necesitas %{signin} o %{signup} para participar." + signin: iniciar sesión + signup: registrarte + cant_answer_verify_html: "Por favor %{verify_link} para poder responder." + verify_link: "verifica tu cuenta" + cant_answer_incoming: "Esta votación todavía no ha comenzado." + cant_answer_expired: "Esta votación ha terminado." + cant_answer_wrong_geozone: "Esta votación no está disponible en tu zona." + vote_answer: "Votar %{answer}" + voted: "Has votado %{answer}" + poll: "Votación" + banner: + featured_title: "#PróximaVotación" + info: "Nuevas propuestas ciudadanas han llegado a la fase de votación." proposal_notifications: new: title: "Enviar mensaje" @@ -409,6 +456,12 @@ es: show: back: "Volver a mi actividad" shared: + edit: 'Editar' + save: 'Guardar' + delete: 'Borrar' + comments: + title: 'Comentarios' + login_to_comment: 'Necesitas %{signin} o %{signup} para comentar.' "yes": "Sí" "no": "No" advanced_search: @@ -474,6 +527,7 @@ es: twitter: Twitter youtube: YouTube whatsapp: WhatsApp + telegram: Telegram spending_proposals: form: association_name_label: 'Si propones en nombre de una asociación o colectivo añade el nombre aquí' diff --git a/config/locales/fr.yml b/config/locales/fr.yml index cc0b1c590..74a65a916 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -854,7 +854,6 @@ fr: description: Ce portail utiliser %{consul} qui est %{open_source}. De Madrid ouvert sur le monde. faq: assistance technique - more_info: Plus d'information open_data_text: Tout les détails de la Mairie sont vôtre pour accès open_data_title: Open data open_source: logiciel libre @@ -876,7 +875,7 @@ fr: locale: 'Langue :' logo: Consul logo moderation: Modération - more_information: Plus d'information + more_info: Plus d'information my_account_link: Mon compte my_activity_link: Mon activité new_notifications: @@ -1167,7 +1166,7 @@ fr: mentionnées dans les conditions d'utilisation de ce portail." conditions: Conditions d'utilisation general_terms: Conditions d'utilisation - more_information: + more_info: description: faq: Questions fréquentes sur les problèmes techniques how_it_works: Découvrez tout ce qui est possible de faire avec ce site @@ -1232,7 +1231,7 @@ fr: titles: accessibility: Accessibilité conditions: Conditions d'utilisation - more_information: Plus d'informations + more_info: Plus d'informations opendata: Open Data privacy: Vie privée verify: @@ -1354,7 +1353,8 @@ fr: official_level_3_name: Niveau 3 personne officielle official_level_4_name: Niveau 4 personne officielle official_level_5_name: Niveau 5 personne officielle - per_page_code: Code à inclure dans chaque page + per_page_code_head: "Code à inclure dans chaque page ()" + per_page_code_body: "Code à inclure dans chaque page ()" proposal_code_prefix: Préfixe pour les codes de propositions votes_for_proposal_success: Nombre de votes nécessaires pour l'approbation d'une proposition @@ -1395,6 +1395,8 @@ fr: facebook: Facebook twitter: Twitter youtube: YouTube + whatsapp: WhatsApp + telegram: Telegram social_share_button: baidu: Baidu.com delicious: Delicious @@ -1413,6 +1415,7 @@ fr: tumblr: Tumblr twitter: Twitter weibo: Sina Weibo + telegram: Telegram spending_proposals: form: description: Description @@ -1629,7 +1632,7 @@ fr: link: Voir les propositions text: Créez vos propres propositions et soutenez celles d'autres citoyens. title: Propositions citoyennes - more_information: Plus d'information sur cette page + more_info: Plus d'information sur cette page proposal: alt: Icône Proposez description: Espace ouvert aux propositions sur le type de ville que nous voulons diff --git a/config/locales/management.en.yml b/config/locales/management.en.yml index 25032010e..d8aaf826c 100644 --- a/config/locales/management.en.yml +++ b/config/locales/management.en.yml @@ -32,6 +32,7 @@ en: under_age: "You don't have the required age to verify your account." verify: Verify email_label: Email + date_of_birth: Date of birth email_verifications: already_verified: This user account is already verified. choose_options: 'Please choose one of the following options:' diff --git a/config/locales/management.es.yml b/config/locales/management.es.yml index 9ac276f3a..10543798a 100644 --- a/config/locales/management.es.yml +++ b/config/locales/management.es.yml @@ -32,6 +32,7 @@ es: under_age: No tienes edad suficiente para verificar tu cuenta. verify: Verificar usuario email_label: Email + date_of_birth: Fecha de nacimiento email_verifications: already_verified: Esta cuenta de usuario ya está verificada. choose_options: 'Elige una de las opciones siguientes:' diff --git a/config/locales/moderation.en.yml b/config/locales/moderation.en.yml index 6304e8d62..66697f5e4 100755 --- a/config/locales/moderation.en.yml +++ b/config/locales/moderation.en.yml @@ -1,6 +1,8 @@ --- en: moderation: + header: + title: Moderation comments: index: block_authors: Block authors diff --git a/config/locales/moderation.es.yml b/config/locales/moderation.es.yml index 2d7c3aea5..b41ba0fbf 100644 --- a/config/locales/moderation.es.yml +++ b/config/locales/moderation.es.yml @@ -1,6 +1,8 @@ --- es: moderation: + header: + title: Moderación comments: index: block_authors: Bloquear autores diff --git a/config/locales/officing.en.yml b/config/locales/officing.en.yml new file mode 100644 index 000000000..ffda9d326 --- /dev/null +++ b/config/locales/officing.en.yml @@ -0,0 +1,103 @@ +--- +en: + officing: + header: + title: Polling + dashboard: + index: + title: Poll officing + info: Here you can validate user documents and store voting results + menu: + voters: Validate document + recounts: Store recount + final_recounts: Final recounts and results + 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 + add_results: Add results + recounts: + flash: + create: "Data added" + error_create: "Count NOT added. Error in data." + new: + title: "%{poll} - Add daily recount" + not_allowed: "You are not a poll officer for this poll" + booth_date: "Booth and date" + select_booth_date: "Select booth and date" + count: "Vote count" + count_placeholder: "Vote count" + submit: Save + 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: "Final vote count" + count_placeholder: "Final vote count" + submit: Save + final_recount_list: "Your final recounts" + system_count: "System recount" + add_results: "Add results" + results: + flash: + create: "Results saved" + error_create: "Results NOT saved. Error in data." + error_wrong_booth: "Wrong booth. Results NOT saved." + error_wrong_date: "Wrong date. Results NOT saved." + new: + title: "%{poll} - Add results" + not_allowed: "You are allowed to add results for this poll" + booth: "Booth" + date: "Date" + select_booth: "Select booth" + select_date: "Select date" + ballots_white: "Blank ballots" + ballots_null: "Invalid ballots" + submit: "Save" + results_list: "Your results" + see_results: "See results" + index: + no_results: "No results" + results: Results + table_answer: Answer + table_votes: Votes + table_whites: "Blank ballots" + table_nulls: "Invalid ballots" + residence: + flash: + create: "Document verified with Census" + not_allowed: "You don't have officing shifts today" + new: + title: Validate document + document_number: "Document number (including letters)" + submit: Validate document + error_verifying_census: "The Census was unable to verify this document." + form_errors: prevented the verification of this document + no_assignments: "You don't have officing shifts today" + voters: + new: + title: Polls + table_poll: Poll + table_actions: Polls status + show: + can_vote: Can vote + error_already_voted: Has already participated in this poll + submit: Confirm vote + success: "Vote introduced!" diff --git a/config/locales/officing.es.yml b/config/locales/officing.es.yml new file mode 100644 index 000000000..7958b64b0 --- /dev/null +++ b/config/locales/officing.es.yml @@ -0,0 +1,103 @@ +--- +es: + officing: + header: + title: Votaciones + dashboard: + index: + title: Presidir mesa de votaciones + info: Aquí puedes validar documentos de ciudadanos y guardar los resultados de las urnas + menu: + voters: "Validar documento y votar" + recounts: "Recuento diario" + final_recounts: "Recuento final y escrutinio" + 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" + add_results: "Añadir resultados" + recounts: + flash: + create: "Datos añadidos" + error_create: "Recuento NO añadido. Error en los datos" + new: + title: "%{poll} - Añadir recuento diario" + not_allowed: "No eres presidente de mesa en esta votación" + booth_date: "Fecha y urna" + select_booth_date: "Elige fecha y urna" + count: "Número de votos" + count_placeholder: "Número de votos" + submit: "Guardar" + 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: "Recuento final" + count_placeholder: "Número final de votos" + submit: "Guardar" + final_recount_list: "Tus recuentos finales" + system_count: "Recuento del sistema" + add_results: "Añadir resultados" + results: + flash: + create: "Datos guardados" + error_create: "Resultados NO añadidos. Error en los datos" + error_wrong_booth: "Urna incorrecta. Resultados NO guardados." + error_wrong_date: "Fecha incorrecta. Resultados NO guardados." + new: + title: "%{poll} - Añadir resultados" + not_allowed: "No tienes permiso para introducir resultados" + booth: "Urna" + date: "Día" + select_booth: "Elige urna" + select_date: "Elige día" + ballots_white: "Papeletas en blanco" + ballots_null: "Papeletas nulas" + submit: "Guardar" + results_list: "Tus resultados" + see_results: "Ver resultados" + index: + no_results: "No hay resultados" + results: "Resultados" + table_answer: Respuesta + table_votes: Votos + table_whites: Papeletas en blanco + table_nulls: Papeletas nulas + residence: + flash: + create: "Documento verificado con el Padrón" + not_allowed: "Hoy no tienes turno de presidente de mesa" + new: + title: Validar documento + document_number: "Número de documento (incluida letra)" + submit: Validar documento + error_verifying_census: El Padrón no pudo verificar este documento. + form_errors: evitaron verificar este documento + no_assignments: "Hoy no tienes turno de presidente de mesa" + voters: + new: + title: Votaciones + table_poll: Votación + table_actions: Estado de las votaciones + show: + can_vote: Puede votar + error_already_voted: "Ya ha participado en esta votación." + submit: Confirmar voto + success: "¡Voto introducido!" diff --git a/config/locales/pages.en.yml b/config/locales/pages.en.yml index 4ac76b1d3..a7b4ca43c 100755 --- a/config/locales/pages.en.yml +++ b/config/locales/pages.en.yml @@ -4,42 +4,57 @@ en: census_terms: To confirm the account, you must be 16 or older and be registered, having provided the information requested previously, will verify. By accepting the verification process, you also consent to the verification of this information, as well as the contact methods featuring in said files. The data provided will be acquired and processed in a file mentioned previously in the terms and conditions of use for the Portal. conditions: Terms and conditions of use general_terms: Terms and Conditions - more_information: - description: - faq: Frecuently asked question about tecnical problems - how_it_works: Find out all you can do with this web site - how_to_use: Use it freely or help us to improve it, it is free software - participation: Citizen participation, transparency and open government - participation_facts: To lose your fear - participation_world: Systems of citizen participation that exist in the world - proposals_info: Create your own proposals - signature_sheet: Sheet collecting signatures for support - spending_proposals_info: Create your own spending proposals - how_it_works: - i: Participation - i1: Debates area - i2: Proposals area - text: |- - The new Open Government Portal is divided in three parts: Participation, Transparency and Open Data (you can see the links in the upper right-hand part). - >**I. Participation:** In this part we can decide which city we want (using citizen proposals, debate areas, participatory budgets , collaborative legislation, and many others we will implement). - **II. Transparency:** In this part information about how the city is managed will be published every day: name, position, salary, contracts, agenda,... of the persons in charge. It is also the place where exercise your right to access information, having the chance to request any information about City Council easily and rapidly. - **III. Open data:** In this part the city council databases are posted, so that anyone can use information directly, without having to ask. It can also be requested the publication of more databases. - - We have opened the new Participation portal and we will open the new Transparency and Open Data seccion shortly (meanwhile the links to existing sites are kept). - ## I. Participation - The participation section will have different ways to participate: citizen proposals, debate areas, participatory budgets , collaborative legislation, and many others. You can currently visit the debate area and the citizen proposals will be ready very soon. - ## I.I. Debates area - In the debates area everyone can open a discussion thread about any topic, creating an independent space where people could debate about the proposed topic. In this way, in this digital space there will be hundreds of different debate spaces which citizens could see, joining the ones they find more interesting. - - Both threads and comments could be valued by anyone, so the citizenship, and not someone in its name, will decide which are the most important issues in every moment. These will be showed in the main page of the space, being able to access also the rest of issues in following pages, or using others order criteria (the most commented, the newst, the most controversial, etc.). - - Every city council employee has its own user, which will be designated as such, allowing them to participate in debates at the same level than the rest of citizens. That will allow creating direct communication spaces between them, avoiding the problems that implies the measured communication, and following clear approach of the new City Council by virtue of which the city council works for the citizenship. - ## I.I. Proposals area - In the proposals area everyone can propose an initiative with the intention of collecting support enough for the idea being consulting to the whole citizenship with binding effect. - - The proposals can be supported by every citizen registered that has verified their account in the participation platform. In this way, the citizenship, and not someone in its name, will decide which are the proposals that are worthwhile to carry out. - - One that the proposal has achive support of 1% census, it will be studied by a city council group and to move beyond the popular referendum phase, in which citizenship will vote if it is carried out or not. The maximum period to obtain support enough is 12 months. + more_info: + title: "Discover %{org_name}" + subtitle: "Learn everything you can do on this website." + guide: 'This guide explains each section of %{org_name}. You can expand the information on "Detailed information" links.' + menu: + debates: "Debates" + proposals: "Proposals" + budgets: "Participatory budgets" + other: "Other information of interest" + debates: + title: "Debates" + description: "Create a thread where you can discuss any topic you want to share with other people in your city." + feature_1: "To create a debate you have to %{link}" + feature_1_link: "sign up on %{org_name}" + feature_2_html: "Debates can be rated using the I agree or I disagree buttons you'll find on each of them." + image_alt: "Buttons to rate the debates" + figcaption: '"I agree" and "I disagree" buttons to rate the debates.' + proposals: + title: "Proposals" + description: "Propose what you want the City Council to carry out and support proposals from other people." + feature_1: "To create a proposal you have to %{link}, in addition to support you must verify your account." + feature_1_link: "sign up on %{org_name}" + feature_2_html: "Proposals that get support from 1% of people will be voted on." + feature_3_html: "If there are more people in favor than against, the City Council will publish their plan to accomplish the proposal and you can track its progress." + image_alt: "Button to support a proposal" + figcaption_html: 'Button to "Support" a proposal.
    When it reaches the number of supports will go to vote.' + budgets: + title: "Participatory Budgeting" + description: "The first six months of each year you can decide how to spend part of the budget." + feature_1: "To submit an investment project you have to %{link} and verify your account." + feature_1_link: "sign up on %{org_name}" + feature_2_html: "The first is the submit phase of investment projects." + feature_3_html: "Then there is a support phase to prioritize the most interesting, the most supported are evaluated by the City to see if they are viable and how much they are worth." + feature_4_html: "At the end there is a vote phase where it is decided on which part of the budget is spent." + image_alt: "Different phases of a participatory budget" + figcaption_html: '"Support phase" and "Voting" phase of participatory budgets.' + polls: + title: "Polls" + description: "Citizen proposals that reach 1% of support will be put to a vote." + feature_1: "To participate in the next poll you have to %{link} and verify your account." + feature_1_link: "sign up on %{org_name}" + feature_2: "All verified users over 16 years old can vote." + feature_3: "The results of all votes shall be binding on the government." + faq: + title: "Technical problems?" + description: "Read the FAQs and solve your questions." + button: "View frequently asked questions" + other: + how_to_use: "Use %{org_name} in your city" + world: "Citizen participation in the world" + facts: "Facts about citizen participation and direct democracy" how_to_use: text: |- Use it in your local government or help us to improve it, it is free software. @@ -48,24 +63,12 @@ en: If you are a programmer, you can see the code and help us to improve it at [Consul app](https://github.com/ayuntamientomadrid 'consul github'). titles: - faq: Solution to tecnical problemas (FAQ) - how_it_works: How does this Open Government Portal work? how_to_use: Use it in your local government - participation: Participation and Transparency y Transparencia - coming news - participation_facts: Facts about citizen participation and direct democracy - participation_world: Direct citizen participation in the world - proposals_info: How does citizen proposals work? - signature_sheet: Signature sheet - spending_proposals_info: How does participatory budgeting work? - opendata: - slogan: "Information about Open Data." - title: Open Data privacy: Privacy Policy titles: accessibility: Accessibility conditions: Terms of use - more_information: More information - opendata: Open Data + more_info: "More information about %{org_name}" privacy: Privacy Policy verify: code: Code you received in letter @@ -74,4 +77,4 @@ en: info_code: 'Now introduce the code you received in letter:' password: Password submit: Verify my account - title: Verify your account + title: Verify your account \ No newline at end of file diff --git a/config/locales/pages.es.yml b/config/locales/pages.es.yml index e3e82effa..27f1afc16 100644 --- a/config/locales/pages.es.yml +++ b/config/locales/pages.es.yml @@ -4,42 +4,57 @@ es: census_terms: Para verificar la cuenta hay que tener 16 años o más y estar empadronado aportando los datos indicados anteriormente, los cuales serán contrastados. Aceptando el proceso de verificación acepta dar su consentimiento para contrastar dicha información, así como medios de contacto que figuren en dichos ficheros. Los datos aportados serán incorporados y tratados en un fichero indicado anteriormente en las condiciones de uso del portal. conditions: Condiciones de uso general_terms: Términos y Condiciones - more_information: - description: - faq: Preguntas frecuentes sobre problemas técnicos - how_it_works: Descubre todo lo que puedes hacer en esta web - how_to_use: Utilízalo libremente o ayúdanos a mejorarlo, es software libre - participation: Participación Ciudadana, Transparencia y Gobierno Abierto - participation_facts: Para perderle el miedo - participation_world: Sistemas de participación ciudadana que ya existen en el mundo - proposals_info: Crea tus propias propuestas - signature_sheet: Hojas de firmas para recoger apoyos - spending_proposals_info: Envía tus propuestas de gasto - how_it_works: - i: Participación - i1: Espacio de debate - i2: Espacio de propuestas - text: |- - El nuevo Portal de Gobierno Abierto está dividido en tres partes: Participación, Transparencia y Datos Abiertos (verás los enlaces en la parte superior derecha). - >**I. Participación:** Donde poder decidir qué ciudad queremos tener (a través de propuestas ciudadanas, espacios de debate, presupuestos participativos, legislación colaborativa, y muchos otros procesos que iremos implementando). - **II. Transparencia:** En este espacio se publicarán todos los datos relativos a quién y cómo se gestiona la ciudad (nombres de los responsables, cargos, sueldos, planes de gobierno, contratos, agendas de los responsables...). Además es el espacio donde ejercer el derecho de acceso a la información, pudiendo solicitar cualquier información sobre el Ayuntamiento de manera fácil y rápida. - **III. Datos Abiertos:** En este espacio se cuelgan las bases de datos que tiene el Ayuntamiento, para que cualquiera pueda usar toda la información directamente, sin necesidad ni siquiera de preguntar. También se puede solicitar la publicación de más bases de datos. - - Inauguramos el nuevo portal con la nueva sección de Participación, y en breve añadiremos las nuevas secciones de Transparencia y Datos Abiertos (así que mantenemos por el momento los enlaces a las páginas al respecto que ya existían). - ## I. Participación - El apartado de participación comprenderá diferentes mecanismos de participación: propuestas ciudadanas, espacios de debate, presupuestos participativos, legislación colaborativa, entre muchos otros. Por el momento presentamos el espacio de debate, y muy pronto las propuestas ciudadanas. - ## I.I. Espacio de debate - En este espacio, cualquier persona puede abrir un hilo de discusión sobre cualquier tema, creando un espacio independiente donde la gente podrá debatir sobre el tema propuesto. De esta manera, en este espacio digital convivirán cada día cientos de espacios de debate distintos entre los que la ciudadanía podrá pasearse, sumándose a los que más les interese. - - Tanto los hilos, como los comentarios podrán ser valorados por cualquiera, de tal manera que será la propia ciudadanía, y nadie en su nombre, la que decida cuáles son los temas más importantes en cada momento. Estos serán presentados en la portada del espacio, pudiendo por supuesto accederse a todos los demás temas en páginas posteriores, o usando otros criterios de ordenación (los temas con más comentarios, los más nuevos, los más controvertidos, etc.). - - Cada uno de los trabajadores del Ayuntamiento tiene un usuario propio, que será resaltado como tal, permitiendo que participen en los debates al mismo nivel que todos los demás ciudadanos. Esto permitirá crear espacios de comunicación directos entre unos y otros, evitando los inconvenientes que implica la comunicación mediada, y respondiendo a un planteamiento claro por parte del nuevo gobierno por el cual el Ayuntamiento trabaja para la ciudadanía, y ante ella debe responder. - ## I.I. Espacio de propuestas - En este espacio, cualquier persona puede proponer una iniciativa con la intención de recabar los suficientes apoyos como para que la idea pase a ser consultada a toda la ciudadanía con carácter vinculante. - - Las propuestas pueden ser apoyadas por ciudadanos empadronados que hayan verificado su cuenta en la plataforma de participación, de tal manera que será la propia ciudadanía, y nadie en su nombre, la que decida cuáles son las propuestas que merecen la pena ser llevadas a cabo. - - Una vez que una propuesta alcance una cantidad de apoyos equivalente al 1% del censo, automáticamente pasa a ser estudiada por un grupo de trabajo del Ayuntamiento y pasará a la siguiente fase de consulta popular, en la que la ciudadanía votará si se lleva a cabo o no. El plazo máximo para recabar los apoyos necesarios será de 12 meses. + more_info: + title: "Descubre %{org_name}" + subtitle: "Aprende todo lo que puedes hacer en esta web." + guide: 'Esta guía explica cada una de las secciones de %{org_name}. Puedes ampliar la información en los enlaces de "Información detallada".' + menu: + debates: "Debates" + proposals: "Propuestas" + budgets: "Presupuestos participativos" + other: "Otra información de interés" + debates: + title: "Debates" + description: "Crea un hilo en el que debatir sobre cualquier tema que quieras compartir con el resto de gente de tu ciudad." + feature_1: "Para crear un debate tienes que %{link}" + feature_1_link: "registrarte en %{org_name}" + feature_2_html: "Los debates pueden ser valorados utilizando los botones de Estoy de acuerdo o No estoy de acuerdo que encontrarás en cada uno de ellos." + image_alt: "Botones para valorar los debates" + figcaption: 'Botones "Estoy de acuerdo" y "No estoy de acuerdo" para valorar los debates.' + proposals: + title: "Propuestas" + description: "Propón lo que quieres que el Ayuntamiento lleve a cabo y apoya propuestas de otras personas." + feature_1: "Para crear una propuesta tienes que %{link}, además para apoyarlas debes verificar tu cuenta." + feature_1_link: "registrarte en %{org_name}" + feature_2_html: "Las propuestas que consigan el apoyo del 1% de la gente (mayor de 16 años empadronada en Madrid; 27.064 apoyos) pasan a votación." + feature_3_html: "Si hay más gente a favor que en contra en la votación, el Ayuntamiento asume la propuesta y se hace." + image_alt: "Botón para apoyar una propuesta" + figcaption_html: 'Botón para "Apoyar" una propuesta.
    Cuando alcance el número de apoyos pasará a votación.' + budgets: + title: "Presupuestos participativos" + description: "Los primeros seis meses de cada año puedes decidir cómo gastar parte del presupuesto." + feature_1: "Para crear un proyecto de gasto tienes que %{link} y verificar tu cuenta." + feature_1_link: "registrarte en %{org_name}" + feature_2_html: "En primer lugar empieza la fase de aceptación de proyectos de gasto." + feature_3_html: "Después hay una fase de apoyos para priorizar lo más interesante, las más apoyadas son evaluadas por el Ayuntamiento para ver si son viables y cuánto valen." + feature_4_html: "Al final hay una fase de votación donde se decide en cuáles se gasta esa parte del presupuesto." + image_alt: "Diferentes fases de un presupuesto participativo" + figcaption_html: 'Fase de "Apoyos" y fase de "Votación" de los presupuestos participativos.' + polls: + title: "Votaciones" + description: "Las propuestas ciudadanas que alcancen el 1% de apoyos pasarán a votación." + feature_1: "Para participar en la próxima votación tienes que %{link} y verificar tu cuenta." + feature_1_link: "registrarte en %{org_name}" + feature_2: "Pueden votar todas las personas empadronadas en la ciudad mayores de 16 años." + feature_3: "Los resultados de todas las votaciones serán vinculantes para el gobierno." + faq: + title: "¿Problemas técnicos?" + description: "Lee las preguntas frecuentes y resuelve tus dudas." + button: "Ver preguntas frecuentes" + other: + how_to_use: "Utiliza %{org_name} en tu municipio" + world: "Participación ciudadana directa en el mundo" + facts: "Hechos sobre participación ciudadana y democracia directa" how_to_use: text: |- Utilízalo en tu municipio libremente o ayúdanos a mejorarlo, es software libre. @@ -48,24 +63,12 @@ es: Si eres programador, puedes ver el código y ayudarnos a mejorarlo en [aplicación Consul](https://github.com/ayuntamientomadrid 'github consul'). titles: - faq: Soluciones a problemas técnicos (FAQ) - how_it_works: "¿Cómo funciona este Portal de Gobierno Abierto?" how_to_use: Utilízalo en tu municipio - participation: Participación y Transparencia - Próximas novedades - participation_facts: Hechos sobre participación ciudadana y democracia directa - participation_world: Participación ciudadana directa en el mundo - proposals_info: "¿Cómo funcionan las propuestas ciudadanas?" - signature_sheet: Hojas de firmas - spending_proposals_info: "¿Cómo funcionan los presupuestos participativos?" - opendata: - slogan: "Información sobre Datos abiertos." - title: Datos abiertos privacy: Política de Privacidad titles: accessibility: Accesibilidad conditions: Condiciones de uso - more_information: Más información - opendata: Datos abiertos + more_info: "Más información sobre %{org_name}" privacy: Política de Privacidad verify: code: Código que has recibido en tu carta @@ -74,4 +77,4 @@ es: info_code: 'Ahora introduce el código que has recibido en tu carta:' password: Contraseña submit: Verificar mi cuenta - title: Verifica tu cuenta + title: Verifica tu cuenta \ No newline at end of file diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index febe0ed5c..298d536ab 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -841,7 +841,6 @@ pt-BR: description: Este portal usa o %{consul} que é %{open_source}. De Madrid para o mundo. faq: suporte técnico - more_info: Mais informações open_data_text: Todos os detalhes sobre a Câmara estão disponíveis para acesso. open_data_title: Dados abertos open_source: programa de código aberto @@ -862,7 +861,7 @@ pt-BR: locale: 'Idioma:' logo: Consul logotipo moderation: Moderação - more_information: Mais informações + more_info: Mais informações my_account_link: Minha conta my_activity_link: Minha atividade new_notifications: @@ -1149,7 +1148,7 @@ pt-BR: mencionado previamente nos termos e condições de uso deste Portal. conditions: Termos e condições de uso general_terms: Termos e condições - more_information: + more_info: description: faq: Perguntas frequentes feitas sobre problemas técnicos how_it_works: Descubra tudo que você pode fazer com este website. @@ -1248,7 +1247,7 @@ pt-BR: titles: accessibility: Acessibilidade conditions: Termos de uso - more_information: Mais informações + more_info: Mais informações opendata: Dados abertos privacy: Política de privacidade verify: @@ -1370,7 +1369,8 @@ pt-BR: official_level_3_name: Nível 3 coordenador público official_level_4_name: Nível 4 coordenador público official_level_5_name: Nível 5 coordenador público - per_page_code: Código a ser incluído em cada página + per_page_code_head: "Código a ser incluído em cada página ()" + per_page_code_body: "Código a ser incluído em cada página ()" proposal_code_prefix: Prefixo para códigos de Proposta votes_for_proposal_success: Número de votos necessários para aprovar uma Proposta shared: @@ -1640,7 +1640,7 @@ pt-BR: link: Mostrar propostas text: Criar suas próprias propostas e apoiar aquelas de outros cidadãos title: Propostas cidadãs - more_information: Mais informações sobre esta página + more_info: Mais informações sobre esta página proposal: alt: "Ícone proposta" description: Espaço aberto para propostas cidadãs sobre o tipo de cidade dentro da qual nós queremos viver. diff --git a/config/locales/responders.en.yml b/config/locales/responders.en.yml index 100ae5f41..099751345 100755 --- a/config/locales/responders.en.yml +++ b/config/locales/responders.en.yml @@ -6,6 +6,8 @@ en: notice: "%{resource_name} created successfully." debate: "Debate created successfully." direct_message: "You message has been sent successfully." + poll: "Poll created successfully." + poll_booth: "Booth created successfully." proposal: "Proposal created successfully." proposal_notification: "Your message has been sent correctly." spending_proposal: "Spending proposal created successfully. You can access it from %{activity}" @@ -16,9 +18,12 @@ en: update: notice: "%{resource_name} updated successfully." debate: "Debate updated successfully." + poll: "Poll updated successfully." + poll_booth: "Booth updated successfully." proposal: "Proposal updated successfully." spending_proposal: "Investment project updated succesfully." budget_investment: "Investment project updated succesfully." destroy: spending_proposal: "Spending proposal deleted succesfully." budget_investment: "Investment project deleted succesfully." + error: "Could not delete" diff --git a/config/locales/responders.es.yml b/config/locales/responders.es.yml index 43eb00669..166809af9 100644 --- a/config/locales/responders.es.yml +++ b/config/locales/responders.es.yml @@ -6,6 +6,8 @@ es: notice: "%{resource_name} creado correctamente." debate: "Debate creado correctamente." direct_message: "Tu mensaje ha sido enviado correctamente." + poll: "Votación creada correctamente." + poll_booth: "Urna creada correctamente." proposal: "Propuesta creada correctamente." proposal_notification: "Tu message ha sido enviado correctamente." spending_proposal: "Propuesta de inversión creada correctamente. Puedes acceder a ella desde %{activity}" @@ -17,8 +19,11 @@ es: notice: "%{resource_name} actualizado correctamente." debate: "Debate actualizado correctamente." proposal: "Propuesta actualizada correctamente." + poll: "Votación actualizada correctamente." + poll_booth: "Urna actualizada correctamente." spending_proposal: "Propuesta de inversión actualizada correctamente." budget_investment: "Propuesta de inversión actualizada correctamente" destroy: spending_proposal: "Propuesta de inversión eliminada." budget_investment: "Propuesta de inversión eliminada." + error: "No se pudo borrar" diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index 811f10557..d96b9db27 100755 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -13,12 +13,16 @@ en: votes_for_proposal_success: "Number of votes necessary for approval of a Proposal" months_to_archive_proposals: "Months to archive Proposals" email_domain_for_officials: "Email domain for public officials" - per_page_code: "Code to be included on every page" + per_page_code_head: "Code to be included on every page ()" + per_page_code_body: "Code to be included on every page ()" twitter_handle: "Twitter handle" twitter_hashtag: "Twitter hashtag" facebook_handle: "Facebook handle" youtube_handle: "Youtube handle" + telegram_handle: "Telegram handle" blog_url: "Blog URL" + transparency_url: "Transparency URL" + opendata_url: "Open Data URL" url: "Main URL" org_name: "Organization" place_name: "Place" @@ -28,6 +32,7 @@ en: facebook_login: Facebook login google_login: Google login debates: Debates + polls: Polls signature_sheets: Signature sheets spending_proposals: Investment projects spending_proposal_features: diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index b4575432e..97ab56dc9 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -13,12 +13,16 @@ es: votes_for_proposal_success: "Número de votos necesarios para aprobar una Propuesta" months_to_archive_proposals: "Meses para archivar las Propuestas" email_domain_for_officials: "Dominio de email para cargos públicos" - per_page_code: "Código a incluir en cada página" + per_page_code_head: "Código a incluir en cada página ()" + per_page_code_body: "Código a incluir en cada página ()" twitter_handle: "Usuario de Twitter" twitter_hashtag: "Hashtag para Twitter" facebook_handle: "Identificador de Facebook" youtube_handle: "Usuario de Youtube" + telegram_handle: "Usuario de Telegram" blog_url: "URL del blog" + transparency_url: "URL de transparencia" + opendata_url: "URL de open data" url: "URL general de la web" org_name: "Nombre de la organización" place_name: "Nombre del lugar" @@ -28,6 +32,7 @@ es: facebook_login: Registro con Facebook google_login: Registro con Google debates: Debates + polls: Votaciones signature_sheets: Hojas de firmas spending_proposals: Propuestas de inversión spending_proposal_features: diff --git a/config/locales/social_share_button.en.yml b/config/locales/social_share_button.en.yml index 84ee6baba..2715c46ab 100644 --- a/config/locales/social_share_button.en.yml +++ b/config/locales/social_share_button.en.yml @@ -16,4 +16,5 @@ en: tumblr: "Tumblr" plurk: "Plurk" pinterest: "Pinterest" - email: "Email" \ No newline at end of file + email: "Email" + telegram: "Telegram" diff --git a/config/locales/social_share_button.es.yml b/config/locales/social_share_button.es.yml index aa0c3f224..1c49ef4f5 100644 --- a/config/locales/social_share_button.es.yml +++ b/config/locales/social_share_button.es.yml @@ -16,4 +16,5 @@ es: tumblr: "Tumblr" plurk: "Plurk" pinterest: "Pinterest" - email: "Correo electrónico" \ No newline at end of file + email: "Correo electrónico" + telegram: "Telegram" diff --git a/config/locales/valuation.en.yml b/config/locales/valuation.en.yml index de19fea09..56cf38f4c 100644 --- a/config/locales/valuation.en.yml +++ b/config/locales/valuation.en.yml @@ -1,6 +1,8 @@ --- en: valuation: + header: + title: Valuation menu: title: Valuation budgets: Participatory budgets diff --git a/config/locales/valuation.es.yml b/config/locales/valuation.es.yml index b588c8521..e45b57e73 100644 --- a/config/locales/valuation.es.yml +++ b/config/locales/valuation.es.yml @@ -1,6 +1,8 @@ --- es: valuation: + header: + title: Evaluación menu: title: Evaluación budgets: Presupuestos participativos diff --git a/config/routes.rb b/config/routes.rb index 8d5e84ad3..5fd94eaaf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -61,8 +61,6 @@ Rails.application.routes.draw do end end - resources :proposal_ballots, only: [:index] - resources :comments, only: [:create, :show], shallow: true do member do post :vote @@ -95,6 +93,12 @@ Rails.application.routes.draw do get :search, on: :collection end + resources :polls, only: [:show, :index] do + resources :questions, only: [:show], controller: 'polls/questions', shallow: true do + post :answer, on: :member + end + end + resources :users, only: [:show] do resources :direct_messages, only: [:new, :create, :show] end @@ -202,6 +206,33 @@ Rails.application.routes.draw do get :search, on: :collection end + scope module: :poll do + resources :polls do + get :search_questions, on: :member + patch :add_question, on: :member + patch :remove_question, on: :member + + resources :booth_assignments, only: [:index, :show, :create, :destroy] do + get :search_booths, on: :collection + end + + resources :officer_assignments, only: [:index, :create, :destroy] do + get :search_officers, on: :collection + get :by_officer, on: :collection + end + + resources :recounts, only: :index + resources :results, only: :index + end + + resources :officers do + get :search, on: :collection + end + + resources :booths + resources :questions + end + resources :verifications, controller: :verifications, only: :index do get :search, on: :collection end @@ -217,6 +248,12 @@ Rails.application.routes.draw do end resources :geozones, only: [:index, :new, :create, :edit, :update, :destroy] + + namespace :site_customization do + resources :pages, except: [:show] + resources :images, only: [:index, :update, :destroy] + resources :content_blocks, except: [:show] + end end namespace :moderation do @@ -305,12 +342,32 @@ Rails.application.routes.draw do end end + namespace :officing do + resources :polls, only: [:index] do + get :final, on: :collection + + resources :recounts, only: [:new, :create] + resources :final_recounts, only: [:new, :create] + resources :results, only: [:new, :create, :index] + end + resource :residence, controller: "residence", only: [:new, :create] + resources :voters, only: [:new, :create] + root to: "dashboard#index" + end + if Rails.env.development? mount LetterOpenerWeb::Engine, at: "/letter_opener" end mount Tolk::Engine => '/translate', :as => 'tolk' + # more info pages + get 'more-information', to: 'pages#show', id: 'more_info/index', as: 'more_info' + get 'more-information/how-to-use', to: 'pages#show', id: 'more_info/how_to_use/index', as: 'how_to_use' + get 'more-information/faq', to: 'pages#show', id: 'more_info/faq/index', as: 'faq' + get 'more-information/participation/facts', to: 'pages#show', id: 'more_info/participation/facts', as: 'participation_facts' + get 'more-information/participation/world', to: 'pages#show', id: 'more_info/participation/world', as: 'participation_world' + # static pages get '/blog' => redirect("http://blog.consul/") resources :pages, path: '/', only: [:show] diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index a59a3c9b9..f7b2f2908 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -2,7 +2,7 @@ require 'database_cleaner' DatabaseCleaner.clean_with :truncation -puts "Creating Settings" +print "Creating Settings" Setting.create(key: 'official_level_1_name', value: 'Empleados públicos') Setting.create(key: 'official_level_2_name', value: 'Organización Municipal') Setting.create(key: 'official_level_3_name', value: 'Directores generales') @@ -20,11 +20,13 @@ Setting.create(key: 'twitter_handle', value: '@consul_dev') Setting.create(key: 'twitter_hashtag', value: '#consul_dev') Setting.create(key: 'facebook_handle', value: 'consul') Setting.create(key: 'youtube_handle', value: 'consul') +Setting.create(key: 'telegram_handle', value: 'consul') Setting.create(key: 'blog_url', value: '/blog') Setting.create(key: 'url', value: 'http://localhost:3000') Setting.create(key: 'org_name', value: 'Consul') Setting.create(key: 'place_name', value: 'City') Setting.create(key: 'feature.debates', value: "true") +Setting.create(key: 'feature.polls', value: "true") Setting.create(key: 'feature.spending_proposals', value: nil) Setting.create(key: 'feature.spending_proposal_features.voting_allowed', value: nil) Setting.create(key: 'feature.budgets', value: "true") @@ -32,7 +34,8 @@ Setting.create(key: 'feature.twitter_login', value: "true") Setting.create(key: 'feature.facebook_login', value: "true") Setting.create(key: 'feature.google_login', value: "true") Setting.create(key: 'feature.signature_sheets', value: "true") -Setting.create(key: 'per_page_code', value: "") +Setting.create(key: 'per_page_code_head', value: "") +Setting.create(key: 'per_page_code_body', value: "") Setting.create(key: 'comments_body_max_length', value: '1000') Setting.create(key: 'mailer_from_name', value: 'Consul') Setting.create(key: 'mailer_from_address', value: 'noreply@consul.dev') @@ -41,14 +44,18 @@ Setting.create(key: 'meta_keywords', value: 'citizen participation, open governm Setting.create(key: 'verification_offices_url', value: 'http://oficinas-atencion-ciudadano.url/') Setting.create(key: 'min_age_to_participate', value: '16') -puts "Creating Geozones" +puts " ✅" +print "Creating Geozones" + +Geozone.create(name: "city") +Geozone.create(name: "Existent District", census_code: "01") ('A'..'Z').each { |i| Geozone.create(name: "District #{i}", external_code: i.ord, census_code: i.ord) } -puts "Creating Users" +puts " ✅" +print "Creating Users" def create_user(email, username = Faker::Name.name) pwd = '12345678' - puts " #{username}" User.create!(username: username, email: email, password: pwd, password_confirmation: pwd, confirmed_at: Time.current, terms_of_service: "1") end @@ -65,6 +72,9 @@ manager.create_manager valuator = create_user('valuator@consul.dev', 'valuator') valuator.create_valuator +poll_officer = create_user('poll_officer@consul.dev', 'Paul O. Fisher') +poll_officer.create_poll_officer + level_2 = create_user('leveltwo@consul.dev', 'level 2') level_2.update(residence_verified_at: Time.current, confirmed_phone: Faker::PhoneNumber.phone_number, document_number: "2222222222", document_type: "1" ) @@ -104,7 +114,8 @@ end org_user_ids = User.organizations.pluck(:id) not_org_users = User.where(['users.id NOT IN(?)', org_user_ids]) -puts "Creating Tags Categories" +puts " ✅" +print "Creating Tags Categories" ActsAsTaggableOn::Tag.create!(name: "Asociaciones", featured: true, kind: "category") ActsAsTaggableOn::Tag.create!(name: "Cultura", featured: true, kind: "category") @@ -122,7 +133,8 @@ ActsAsTaggableOn::Tag.create!(name: "Transparencia", featured: true, kind: "cat ActsAsTaggableOn::Tag.create!(name: "Seguridad y Emergencias", featured: true, kind: "category") ActsAsTaggableOn::Tag.create!(name: "Medio Ambiente", featured: true, kind: "category") -puts "Creating Debates" +puts " ✅" +print "Creating Debates" tags = Faker::Lorem.words(25) (1..30).each do @@ -135,7 +147,6 @@ tags = Faker::Lorem.words(25) tag_list: tags.sample(3).join(','), geozone: Geozone.reorder("RANDOM()").first, terms_of_service: "1") - puts " #{debate.title}" end @@ -150,11 +161,11 @@ tags = ActsAsTaggableOn::Tag.where(kind: 'category') tag_list: tags.sample(3).join(','), geozone: Geozone.reorder("RANDOM()").first, terms_of_service: "1") - puts " #{debate.title}" end -puts "Creating Proposals" +puts " ✅" +print "Creating Proposals" tags = Faker::Lorem.words(25) (1..30).each do |i| @@ -171,13 +182,33 @@ tags = Faker::Lorem.words(25) tag_list: tags.sample(3).join(','), geozone: Geozone.reorder("RANDOM()").first, terms_of_service: "1") - puts " #{proposal.title}" end -puts "Creating Archived Proposals" +puts " ✅" +print "Creating Archived Proposals" tags = Faker::Lorem.words(25) (1..5).each do + author = User.reorder("RANDOM()").first + description = "

    #{Faker::Lorem.paragraphs.join('

    ')}

    " + proposal = Proposal.create!(author: author, + title: Faker::Lorem.sentence(3).truncate(60), + question: Faker::Lorem.sentence(3) + "?", + summary: Faker::Lorem.sentence(3), + responsible_name: Faker::Name.name, + external_url: Faker::Internet.url, + description: description, + tag_list: tags.sample(3).join(','), + geozone: Geozone.reorder("RANDOM()").first, + terms_of_service: "1", + created_at: Setting["months_to_archive_proposals"].to_i.months.ago) +end + +puts " ✅" +print "Creating Successful Proposals" + +tags = Faker::Lorem.words(25) +(1..10).each do |i| author = User.reorder("RANDOM()").first description = "

    #{Faker::Lorem.paragraphs.join('

    ')}

    " proposal = Proposal.create!(author: author, @@ -191,8 +222,7 @@ tags = Faker::Lorem.words(25) tag_list: tags.sample(3).join(','), geozone: Geozone.reorder("RANDOM()").first, terms_of_service: "1", - created_at: Setting["months_to_archive_proposals"].to_i.months.ago) - puts " #{proposal.title}" + cached_votes_up: Setting["votes_for_proposal_success"]) end @@ -211,11 +241,11 @@ tags = ActsAsTaggableOn::Tag.where(kind: 'category') tag_list: tags.sample(3).join(','), geozone: Geozone.reorder("RANDOM()").first, terms_of_service: "1") - puts " #{proposal.title}" end -puts "Commenting Debates" +puts " ✅" +print "Commenting Debates" (1..100).each do author = User.reorder("RANDOM()").first @@ -227,7 +257,8 @@ puts "Commenting Debates" end -puts "Commenting Proposals" +puts " ✅" +print "Commenting Proposals" (1..100).each do |i| author = User.reorder("RANDOM()").first @@ -239,7 +270,8 @@ puts "Commenting Proposals" end -puts "Commenting Comments" +puts " ✅" +print "Commenting Comments" (1..200).each do author = User.reorder("RANDOM()").first @@ -253,7 +285,8 @@ puts "Commenting Comments" end -puts "Voting Debates, Proposals & Comments" +puts " ✅" +print "Voting Debates, Proposals & Comments" (1..100).each do voter = not_org_users.reorder("RANDOM()").first @@ -276,7 +309,8 @@ end end -puts "Flagging Debates & Comments" +puts " ✅" +print "Flagging Debates & Comments" (1..40).each do debate = Debate.reorder("RANDOM()").first @@ -296,7 +330,8 @@ end Flag.flag(flagger, proposal) end -puts "Creating Spending Proposals" +puts " ✅" +print "Creating Spending Proposals" tags = Faker::Lorem.words(10) @@ -319,17 +354,18 @@ tags = Faker::Lorem.words(10) tag_list: tags.sample(3).join(','), price: rand(1000000), terms_of_service: "1") - puts " #{spending_proposal.title}" end -puts "Creating Valuation Assignments" +puts " ✅" +print "Creating Valuation Assignments" (1..17).to_a.sample.times do SpendingProposal.reorder("RANDOM()").first.valuators << valuator.valuator end -puts "Creating Budgets" +puts " ✅" +print "Creating Budgets" Budget::PHASES.each_with_index do |phase, i| descriptions = Hash[Budget::PHASES.map{ |p| ["description_#{p}", @@ -342,8 +378,6 @@ Budget::PHASES.each_with_index do |phase, i| ) ) - puts budget.name - (1..([1, 2, 3].sample)).each do group = budget.groups.create!(name: Faker::StarWars.planet) @@ -354,13 +388,12 @@ Budget::PHASES.each_with_index do |phase, i| price: rand(1 .. 100) * 100000) end - print "#{group.name} " end - puts "" end -puts "Creating Investments" +puts " ✅" +print "Creating Investments" tags = Faker::Lorem.words(10) (1..100).each do |i| heading = Budget::Heading.reorder("RANDOM()").first @@ -380,47 +413,53 @@ tags = Faker::Lorem.words(10) tag_list: tags.sample(3).join(','), price: rand(1 .. 100) * 100000, terms_of_service: "1") - puts " #{investment.title}" end -puts "Selecting Investments" +puts " ✅" +print "Selecting Investments" Budget.balloting.reorder("RANDOM()").limit(3).each do |budget| budget.investments.feasible.reorder("RANDOM()").limit(10).update_all(selected: true) end -puts "Creating Valuation Assignments" +puts " ✅" +print "Creating Valuation Assignments" -(1..17).to_a.sample.times do +(1..50).to_a.sample.times do Budget::Investment.reorder("RANDOM()").first.valuators << valuator.valuator end -puts "Creating Legislation" +puts " ✅" +print "Creating Legislation" Legislation.create!(title: 'Participatory Democracy', body: 'In order to achieve...') -puts "Ignoring flags in Debates, comments & proposals" +puts " ✅" +print "Ignoring flags in Debates, comments & proposals" Debate.flagged.reorder("RANDOM()").limit(10).each(&:ignore_flag) Comment.flagged.reorder("RANDOM()").limit(30).each(&:ignore_flag) Proposal.flagged.reorder("RANDOM()").limit(10).each(&:ignore_flag) -puts "Hiding debates, comments & proposals" +puts " ✅" +print "Hiding debates, comments & proposals" Comment.with_hidden.flagged.reorder("RANDOM()").limit(30).each(&:hide) Debate.with_hidden.flagged.reorder("RANDOM()").limit(5).each(&:hide) Proposal.with_hidden.flagged.reorder("RANDOM()").limit(10).each(&:hide) -puts "Confirming hiding in debates, comments & proposals" +puts " ✅" +print "Confirming hiding in debates, comments & proposals" Comment.only_hidden.flagged.reorder("RANDOM()").limit(10).each(&:confirm_hide) Debate.only_hidden.flagged.reorder("RANDOM()").limit(5).each(&:confirm_hide) Proposal.only_hidden.flagged.reorder("RANDOM()").limit(5).each(&:confirm_hide) -puts "Creating banners" +puts " ✅" +print "Creating banners" Proposal.last(3).each do |proposal| title = Faker::Lorem.sentence(word_count = 3) @@ -435,5 +474,136 @@ Proposal.last(3).each do |proposal| post_started_at: rand((Time.current - 1.week) .. (Time.current - 1.day)), post_ended_at: rand((Time.current - 1.day) .. (Time.current + 1.week)), created_at: rand((Time.current - 1.week) .. Time.current)) - puts " #{banner.title}" end + +puts " ✅" +print "Creating polls" + +puts " ✅" +print "Active Polls" +(1..3).each do |i| + poll = Poll.create(name: "Active Poll #{i}", + starts_at: 1.month.ago, + ends_at: 1.month.from_now, + geozone_restricted: false) +end +(4..5).each do |i| + poll = Poll.create(name: "Active Poll #{i}", + starts_at: 1.month.ago, + ends_at: 1.month.from_now, + geozone_restricted: true, + geozones: Geozone.reorder("RANDOM()").limit(3) + ) +end + + + + + +puts " ✅" +print "Upcoming Poll" +poll = Poll.create(name: "Upcoming Poll", + starts_at: 1.month.from_now, + ends_at: 2.months.from_now) + +puts " ✅" +print "Expired Poll" +poll = Poll.create(name: "Expired Poll", + starts_at: 2.months.ago, + ends_at: 1.months.ago) + +puts " ✅" +print "Creating Poll Questions" + +(1..50).each do |i| + poll = Poll.reorder("RANDOM()").first + author = User.reorder("RANDOM()").first + description = "

    #{Faker::Lorem.paragraphs.join('

    ')}

    " + open_at = rand(2.months.ago .. 2.months.from_now) + question = Poll::Question.create!(author: author, + title: Faker::Lorem.sentence(3).truncate(60), + description: description, + valid_answers: Faker::Lorem.words((2..7).to_a.sample).join(', '), + poll: poll) +end + +puts " ✅" +print "Creating Poll Booths" +30.times.each_with_index do |i| + Poll::Booth.create(name: "Booth #{i}", polls: [Poll.all.sample]) +end + +puts " ✅" +print "Creating Booth Assignments" +Poll::Booth.all.each do |booth| + Poll::BoothAssignment.create(booth: booth, poll: Poll.all.sample) +end + +puts " ✅" +print "Creating Poll Officer Assignments" +(1..15).to_a.sample.times do |i| + Poll::BoothAssignment.all.sample(i).each do |booth_assignment| + Poll::OfficerAssignment.create(officer: poll_officer.poll_officer, + booth_assignment: booth_assignment, + date: booth_assignment.poll.starts_at) + end +end + +puts " ✅" +print "Creating Poll Recounts" do +(1..15).to_a.sample.times do |i| + poll_officer.poll_officer.officer_assignments.all.sample(i).each do |officer_assignment| + Poll::Recount.create(officer_assignment: officer_assignment, + booth_assignment: officer_assignment.booth_assignment, + date: officer_assignment.date, + count: (1..5000).to_a.sample) + end +end + +end + +puts " ✅" +print "Creating Poll Questions from Proposals" + +(1..3).each do + proposal = Proposal.reorder("RANDOM()").first + poll = Poll.current.first + question = Poll::Question.create(valid_answers: "Yes, No") + question.copy_attributes_from_proposal(proposal) + question.save! +end + +puts " ✅" +print "Creating Successful Proposals" + +(1..10).each do + proposal = Proposal.reorder("RANDOM()").first + poll = Poll.current.first + question = Poll::Question.create(valid_answers: "Yes, No") + question.copy_attributes_from_proposal(proposal) + question.save! +end + +puts " ✅" +print "Commenting Poll Questions" + +(1..30).each do + author = User.reorder("RANDOM()").first + question = Poll::Question.reorder("RANDOM()").first + Comment.create!(user: author, + created_at: rand(question.created_at .. Time.current), + commentable: question, + body: Faker::Lorem.sentence) +end + +puts " ✅" +print "Creating Poll Voters" + +(1..10).each do + poll = Poll.all.sample + user = User.level_two_verified.sample + Poll::Voter.create(poll: poll, user: user) +end + +puts " ✅" +puts "All dev seeds created successfuly 👍" diff --git a/db/migrate/20160914110004_create_polls.rb b/db/migrate/20160914110004_create_polls.rb new file mode 100644 index 000000000..9dd67d9ba --- /dev/null +++ b/db/migrate/20160914110004_create_polls.rb @@ -0,0 +1,7 @@ +class CreatePolls < ActiveRecord::Migration + def change + create_table :polls do |t| + t.string :name + end + end +end diff --git a/db/migrate/20160914110039_create_poll_officers.rb b/db/migrate/20160914110039_create_poll_officers.rb new file mode 100644 index 000000000..329220d40 --- /dev/null +++ b/db/migrate/20160914110039_create_poll_officers.rb @@ -0,0 +1,7 @@ +class CreatePollOfficers < ActiveRecord::Migration + def change + create_table :poll_officers do |t| + t.integer :user_id + end + end +end diff --git a/db/migrate/20160914172016_create_poll_voters.rb b/db/migrate/20160914172016_create_poll_voters.rb new file mode 100644 index 000000000..df5bb4585 --- /dev/null +++ b/db/migrate/20160914172016_create_poll_voters.rb @@ -0,0 +1,9 @@ +class CreatePollVoters < ActiveRecord::Migration + def change + create_table :poll_voters do |t| + t.integer :booth_id + t.string :document_number + t.string :document_type + end + end +end diff --git a/db/migrate/20160914172535_create_poll_booths.rb b/db/migrate/20160914172535_create_poll_booths.rb new file mode 100644 index 000000000..a474aba49 --- /dev/null +++ b/db/migrate/20160914172535_create_poll_booths.rb @@ -0,0 +1,8 @@ +class CreatePollBooths < ActiveRecord::Migration + def change + create_table :poll_booths do |t| + t.string :name + t.integer :poll_id + end + end +end diff --git a/db/migrate/20160926090107_add_location_to_booths.rb b/db/migrate/20160926090107_add_location_to_booths.rb new file mode 100644 index 000000000..146f55d41 --- /dev/null +++ b/db/migrate/20160926090107_add_location_to_booths.rb @@ -0,0 +1,5 @@ +class AddLocationToBooths < ActiveRecord::Migration + def change + add_column :poll_booths, :location, :string + end +end diff --git a/db/migrate/20160928113143_create_officing_booths.rb b/db/migrate/20160928113143_create_officing_booths.rb new file mode 100644 index 000000000..00483fe00 --- /dev/null +++ b/db/migrate/20160928113143_create_officing_booths.rb @@ -0,0 +1,9 @@ +class CreateOfficingBooths < ActiveRecord::Migration + def change + create_table :poll_officing_booths do |t| + t.belongs_to :officer + t.belongs_to :booth + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161020112156_add_dates_to_polls.rb b/db/migrate/20161020112156_add_dates_to_polls.rb new file mode 100644 index 000000000..d504930ae --- /dev/null +++ b/db/migrate/20161020112156_add_dates_to_polls.rb @@ -0,0 +1,6 @@ +class AddDatesToPolls < ActiveRecord::Migration + def change + add_column :polls, :starts_at, :datetime + add_column :polls, :ends_at, :datetime + end +end diff --git a/db/migrate/20161028104156_create_poll_questions.rb b/db/migrate/20161028104156_create_poll_questions.rb new file mode 100644 index 000000000..13c8ac1e8 --- /dev/null +++ b/db/migrate/20161028104156_create_poll_questions.rb @@ -0,0 +1,21 @@ +class CreatePollQuestions < ActiveRecord::Migration + def change + create_table :poll_questions do |t| + t.references :proposal, index: true, foreign_key: true + t.references :poll, index: true, foreign_key: true + t.references :author, index: true # foreign key added later due to rails 4 + t.string :author_visible_name + t.string :title + t.string :question + t.string :summary + t.string :valid_answers + t.text :description + t.integer :comments_count + t.datetime :hidden_at + + t.timestamps + end + + add_foreign_key :poll_questions, :users, column: :author_id + end +end diff --git a/db/migrate/20161028143204_create_geozones_poll_questions.rb b/db/migrate/20161028143204_create_geozones_poll_questions.rb new file mode 100644 index 000000000..68077c510 --- /dev/null +++ b/db/migrate/20161028143204_create_geozones_poll_questions.rb @@ -0,0 +1,10 @@ +class CreateGeozonesPollQuestions < ActiveRecord::Migration + def change + create_table :geozones_poll_questions do |t| + t.references :geozone, index: true, foreign_key: true + t.integer :question_id, index: true + end + + add_foreign_key :geozones_poll_questions, :poll_questions, column: :question_id + end +end diff --git a/db/migrate/20161107124207_add_all_geozones_to_poll_questions.rb b/db/migrate/20161107124207_add_all_geozones_to_poll_questions.rb new file mode 100644 index 000000000..15ed41ba5 --- /dev/null +++ b/db/migrate/20161107124207_add_all_geozones_to_poll_questions.rb @@ -0,0 +1,5 @@ +class AddAllGeozonesToPollQuestions < ActiveRecord::Migration + def change + add_column :poll_questions, :all_geozones, :boolean, default: false + end +end diff --git a/db/migrate/20161107174423_create_poll_partial_result.rb b/db/migrate/20161107174423_create_poll_partial_result.rb new file mode 100644 index 000000000..a4f91a4f9 --- /dev/null +++ b/db/migrate/20161107174423_create_poll_partial_result.rb @@ -0,0 +1,14 @@ +class CreatePollPartialResult < ActiveRecord::Migration + def change + create_table :poll_partial_results do |t| + t.integer :question_id, index: true + t.integer :author_id, index: true + t.string :answer, index: true + t.integer :amount + t.string :origin, index: true + end + + add_foreign_key(:poll_partial_results, :users, column: :author_id) + add_foreign_key(:poll_partial_results, :poll_questions, column: :question_id) + end +end diff --git a/db/migrate/20161122101702_remove_question_from_poll_questions.rb b/db/migrate/20161122101702_remove_question_from_poll_questions.rb new file mode 100644 index 000000000..2532b2948 --- /dev/null +++ b/db/migrate/20161122101702_remove_question_from_poll_questions.rb @@ -0,0 +1,5 @@ +class RemoveQuestionFromPollQuestions < ActiveRecord::Migration + def change + remove_column :poll_questions, :question + end +end diff --git a/db/migrate/20161129011737_add_tsv_to_poll_questions.rb b/db/migrate/20161129011737_add_tsv_to_poll_questions.rb new file mode 100644 index 000000000..a6a44f13d --- /dev/null +++ b/db/migrate/20161129011737_add_tsv_to_poll_questions.rb @@ -0,0 +1,11 @@ +class AddTsvToPollQuestions < ActiveRecord::Migration + def up + add_column :poll_questions, :tsv, :tsvector + add_index :poll_questions, :tsv, using: "gin" + end + + def down + remove_index :poll_questions, :tsv + remove_column :poll_questions, :tsv + end +end diff --git a/db/migrate/20161130122604_remove_poll_id_from_booth.rb b/db/migrate/20161130122604_remove_poll_id_from_booth.rb new file mode 100644 index 000000000..a84b1a70a --- /dev/null +++ b/db/migrate/20161130122604_remove_poll_id_from_booth.rb @@ -0,0 +1,5 @@ +class RemovePollIdFromBooth < ActiveRecord::Migration + def change + remove_column :poll_booths, :poll_id, :integer + end +end diff --git a/db/migrate/20161130123347_create_poll_booth_assignments.rb b/db/migrate/20161130123347_create_poll_booth_assignments.rb new file mode 100644 index 000000000..36a2b9d64 --- /dev/null +++ b/db/migrate/20161130123347_create_poll_booth_assignments.rb @@ -0,0 +1,9 @@ +class CreatePollBoothAssignments < ActiveRecord::Migration + def change + create_table :poll_booth_assignments do |t| + t.integer :booth_id + t.integer :poll_id + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161206130836_delete_officing_booths.rb b/db/migrate/20161206130836_delete_officing_booths.rb new file mode 100644 index 000000000..2ef1180ab --- /dev/null +++ b/db/migrate/20161206130836_delete_officing_booths.rb @@ -0,0 +1,8 @@ +class DeleteOfficingBooths < ActiveRecord::Migration + def self.up + drop_table :poll_officing_booths + end + + def self.down + end +end diff --git a/db/migrate/20161206131125_create_poll_officer_assignments.rb b/db/migrate/20161206131125_create_poll_officer_assignments.rb new file mode 100644 index 000000000..f3a40017c --- /dev/null +++ b/db/migrate/20161206131125_create_poll_officer_assignments.rb @@ -0,0 +1,9 @@ +class CreatePollOfficerAssignments < ActiveRecord::Migration + def change + create_table :poll_officer_assignments do |t| + t.integer :booth_assignment_id + t.integer :officer_id + t.timestamps null: false + end + end +end diff --git a/db/migrate/20161206132126_rename_booth_id_to_booth_assignment_id.rb b/db/migrate/20161206132126_rename_booth_id_to_booth_assignment_id.rb new file mode 100644 index 000000000..a1b59c749 --- /dev/null +++ b/db/migrate/20161206132126_rename_booth_id_to_booth_assignment_id.rb @@ -0,0 +1,10 @@ +class RenameBoothIdToBoothAssignmentId < ActiveRecord::Migration + def change + remove_column :poll_voters, :booth_id, :integer + + add_column :poll_voters, :booth_assignment_id, :integer, null: false + add_column :poll_voters, :poll_id, :integer, null: false + add_column :poll_voters, :created_at, :datetime, null: false + add_column :poll_voters, :updated_at, :datetime, null: false + end +end diff --git a/db/migrate/20161207181001_remove_poll_id_from_voter.rb b/db/migrate/20161207181001_remove_poll_id_from_voter.rb new file mode 100644 index 000000000..5a7012460 --- /dev/null +++ b/db/migrate/20161207181001_remove_poll_id_from_voter.rb @@ -0,0 +1,5 @@ +class RemovePollIdFromVoter < ActiveRecord::Migration + def change + remove_column :poll_voters, :poll_id, :integer + end +end diff --git a/db/migrate/20161227140109_add_date_to_officer_assignment.rb b/db/migrate/20161227140109_add_date_to_officer_assignment.rb new file mode 100644 index 000000000..b7c88a37b --- /dev/null +++ b/db/migrate/20161227140109_add_date_to_officer_assignment.rb @@ -0,0 +1,5 @@ +class AddDateToOfficerAssignment < ActiveRecord::Migration + def change + add_column :poll_officer_assignments, :date, :datetime + end +end diff --git a/db/migrate/20170102114446_create_poll_recounts.rb b/db/migrate/20170102114446_create_poll_recounts.rb new file mode 100644 index 000000000..9643f5b0a --- /dev/null +++ b/db/migrate/20170102114446_create_poll_recounts.rb @@ -0,0 +1,15 @@ +class CreatePollRecounts < ActiveRecord::Migration + def change + create_table :poll_recounts do |t| + t.integer :booth_assignment_id + t.integer :officer_assignment_id + t.integer :count + t.text :count_log, default: "" + + t.timestamps null: false + end + + add_index :poll_recounts, :booth_assignment_id + add_index :poll_recounts, :officer_assignment_id + end +end diff --git a/db/migrate/20170102170125_add_published_to_polls.rb b/db/migrate/20170102170125_add_published_to_polls.rb new file mode 100644 index 000000000..51da03f34 --- /dev/null +++ b/db/migrate/20170102170125_add_published_to_polls.rb @@ -0,0 +1,5 @@ +class AddPublishedToPolls < ActiveRecord::Migration + def change + add_column :polls, :published, :boolean, default: false + end +end diff --git a/db/migrate/20170103191711_add_date_and_officer_assignment_log_to_poll_recounts.rb b/db/migrate/20170103191711_add_date_and_officer_assignment_log_to_poll_recounts.rb new file mode 100644 index 000000000..e0f813500 --- /dev/null +++ b/db/migrate/20170103191711_add_date_and_officer_assignment_log_to_poll_recounts.rb @@ -0,0 +1,6 @@ +class AddDateAndOfficerAssignmentLogToPollRecounts < ActiveRecord::Migration + def change + add_column :poll_recounts, :date, :datetime + add_column :poll_recounts, :officer_assignment_id_log, :text, default: "" + end +end diff --git a/db/migrate/20170120153244_move_geozones_from_poll_questions_to_polls.rb b/db/migrate/20170120153244_move_geozones_from_poll_questions_to_polls.rb new file mode 100644 index 000000000..a2606c370 --- /dev/null +++ b/db/migrate/20170120153244_move_geozones_from_poll_questions_to_polls.rb @@ -0,0 +1,10 @@ +class MoveGeozonesFromPollQuestionsToPolls < ActiveRecord::Migration + def change + drop_table :geozones_poll_questions + + create_table :geozones_polls do |t| + t.references :geozone, index: true, foreign_key: true + t.references :poll, index: true, foreign_key: true + end + end +end diff --git a/db/migrate/20170120161058_add_geozone_restricted_to_polls.rb b/db/migrate/20170120161058_add_geozone_restricted_to_polls.rb new file mode 100644 index 000000000..d6ed3a2c7 --- /dev/null +++ b/db/migrate/20170120161058_add_geozone_restricted_to_polls.rb @@ -0,0 +1,5 @@ +class AddGeozoneRestrictedToPolls < ActiveRecord::Migration + def change + add_column :polls, :geozone_restricted, :boolean, default: false, index: true + end +end diff --git a/db/migrate/20170120164547_remove_all_geozones_from_poll_questions.rb b/db/migrate/20170120164547_remove_all_geozones_from_poll_questions.rb new file mode 100644 index 000000000..b4fcecd20 --- /dev/null +++ b/db/migrate/20170120164547_remove_all_geozones_from_poll_questions.rb @@ -0,0 +1,5 @@ +class RemoveAllGeozonesFromPollQuestions < ActiveRecord::Migration + def change + remove_column :poll_questions, :all_geozones, :boolean + end +end diff --git a/db/migrate/20170125104542_add_poll_id_and_stats_fields_to_poll_voter.rb b/db/migrate/20170125104542_add_poll_id_and_stats_fields_to_poll_voter.rb new file mode 100644 index 000000000..84e7de5d9 --- /dev/null +++ b/db/migrate/20170125104542_add_poll_id_and_stats_fields_to_poll_voter.rb @@ -0,0 +1,12 @@ +class AddPollIdAndStatsFieldsToPollVoter < ActiveRecord::Migration + def change + add_column :poll_voters, :poll_id, :integer, null: false + + remove_column :poll_voters, :booth_assignment_id, :integer, null: false + add_column :poll_voters, :booth_assignment_id, :integer + + add_column :poll_voters, :age, :integer + add_column :poll_voters, :gender, :string + add_column :poll_voters, :geozone_id, :integer + end +end diff --git a/db/migrate/20170125112017_create_poll_answers.rb b/db/migrate/20170125112017_create_poll_answers.rb new file mode 100644 index 000000000..7ea930d0c --- /dev/null +++ b/db/migrate/20170125112017_create_poll_answers.rb @@ -0,0 +1,15 @@ +class CreatePollAnswers < ActiveRecord::Migration + def change + create_table :poll_answers do |t| + t.integer :question_id + t.integer :author_id + t.string :answer + + t.timestamps + end + + add_index :poll_answers, :question_id + add_index :poll_answers, :author_id + add_index :poll_answers, [:question_id, :answer] + end +end diff --git a/db/migrate/20170125114952_add_answer_id_to_poll_voters.rb b/db/migrate/20170125114952_add_answer_id_to_poll_voters.rb new file mode 100644 index 000000000..34d5bbc54 --- /dev/null +++ b/db/migrate/20170125114952_add_answer_id_to_poll_voters.rb @@ -0,0 +1,9 @@ +class AddAnswerIdToPollVoters < ActiveRecord::Migration + def change + add_column :poll_voters, :answer_id, :integer, default: nil + + add_index :poll_voters, :document_number + add_index :poll_voters, :poll_id + add_index :poll_voters, [:poll_id, :document_number, :document_type], name: 'doc_by_poll' + end +end diff --git a/db/migrate/20170127173553_add_officer_assignment_to_votes.rb b/db/migrate/20170127173553_add_officer_assignment_to_votes.rb new file mode 100644 index 000000000..1942ef08f --- /dev/null +++ b/db/migrate/20170127173553_add_officer_assignment_to_votes.rb @@ -0,0 +1,5 @@ +class AddOfficerAssignmentToVotes < ActiveRecord::Migration + def change + add_column :poll_voters, :officer_assignment_id, :integer, default: nil + end +end diff --git a/db/migrate/20170128214244_adds_user_id_to_poll_voters.rb b/db/migrate/20170128214244_adds_user_id_to_poll_voters.rb new file mode 100644 index 000000000..edf635cfc --- /dev/null +++ b/db/migrate/20170128214244_adds_user_id_to_poll_voters.rb @@ -0,0 +1,5 @@ +class AddsUserIdToPollVoters < ActiveRecord::Migration + def change + add_reference :poll_voters, :user, index: true + end +end diff --git a/db/migrate/20170130101121_create_poll_final_recount.rb b/db/migrate/20170130101121_create_poll_final_recount.rb new file mode 100644 index 000000000..07723b708 --- /dev/null +++ b/db/migrate/20170130101121_create_poll_final_recount.rb @@ -0,0 +1,15 @@ +class CreatePollFinalRecount < ActiveRecord::Migration + def change + create_table :poll_final_recounts do |t| + t.integer :booth_assignment_id + t.integer :officer_assignment_id + t.integer :count + t.text :count_log, default: "" + t.datetime :created_at, null: false + t.datetime :updated_at, null: false + t.text :officer_assignment_id_log, default: "" + end + + add_index :poll_final_recounts, :booth_assignment_id + end +end diff --git a/db/migrate/20170130103550_add_final_to_poll_officer_assignments.rb b/db/migrate/20170130103550_add_final_to_poll_officer_assignments.rb new file mode 100644 index 000000000..4cafb5ffb --- /dev/null +++ b/db/migrate/20170130103550_add_final_to_poll_officer_assignments.rb @@ -0,0 +1,5 @@ +class AddFinalToPollOfficerAssignments < ActiveRecord::Migration + def change + add_column :poll_officer_assignments, :final, :boolean, default: false + end +end diff --git a/db/migrate/20170130133736_add_date_to_final_recount.rb b/db/migrate/20170130133736_add_date_to_final_recount.rb new file mode 100644 index 000000000..14d0f564c --- /dev/null +++ b/db/migrate/20170130133736_add_date_to_final_recount.rb @@ -0,0 +1,5 @@ +class AddDateToFinalRecount < ActiveRecord::Migration + def change + add_column :poll_final_recounts, :date, :datetime, null: false + end +end 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/migrate/20170130171322_remove_summary_from_poll_question.rb b/db/migrate/20170130171322_remove_summary_from_poll_question.rb new file mode 100644 index 000000000..02c5fb68e --- /dev/null +++ b/db/migrate/20170130171322_remove_summary_from_poll_question.rb @@ -0,0 +1,5 @@ +class RemoveSummaryFromPollQuestion < ActiveRecord::Migration + def change + remove_column :poll_questions, :summary + end +end diff --git a/db/migrate/20170201113206_adds_fields_to_poll_partial_results.rb b/db/migrate/20170201113206_adds_fields_to_poll_partial_results.rb new file mode 100644 index 000000000..3755dc700 --- /dev/null +++ b/db/migrate/20170201113206_adds_fields_to_poll_partial_results.rb @@ -0,0 +1,9 @@ +class AddsFieldsToPollPartialResults < ActiveRecord::Migration + def change + add_column :poll_partial_results, :date, :date + add_column :poll_partial_results, :booth_assignment_id, :integer + add_column :poll_partial_results, :officer_assignment_id, :integer + + add_index :poll_partial_results, [:booth_assignment_id, :date] + end +end diff --git a/db/migrate/20170202151151_add_log_fields_to_poll_partial_results.rb b/db/migrate/20170202151151_add_log_fields_to_poll_partial_results.rb new file mode 100644 index 000000000..86966aed4 --- /dev/null +++ b/db/migrate/20170202151151_add_log_fields_to_poll_partial_results.rb @@ -0,0 +1,7 @@ +class AddLogFieldsToPollPartialResults < ActiveRecord::Migration + def change + add_column :poll_partial_results, :amount_log, :text, default: "" + add_column :poll_partial_results, :officer_assignment_id_log, :text, default: "" + add_column :poll_partial_results, :author_id_log, :text, default: "" + end +end diff --git a/db/migrate/20170203163304_create_poll_white_results.rb b/db/migrate/20170203163304_create_poll_white_results.rb new file mode 100644 index 000000000..2ebfc93b1 --- /dev/null +++ b/db/migrate/20170203163304_create_poll_white_results.rb @@ -0,0 +1,18 @@ +class CreatePollWhiteResults < ActiveRecord::Migration + def change + create_table :poll_white_results do |t| + t.integer :author_id + t.integer :amount + t.string :origin + t.date :date + t.integer :booth_assignment_id + t.integer :officer_assignment_id + t.text :amount_log, default: "" + t.text :officer_assignment_id_log, default: "" + t.text :author_id_log, default: "" + end + + add_index :poll_white_results, :officer_assignment_id + add_index :poll_white_results, :booth_assignment_id + end +end diff --git a/db/migrate/20170203163317_create_poll_null_results.rb b/db/migrate/20170203163317_create_poll_null_results.rb new file mode 100644 index 000000000..9c138cf65 --- /dev/null +++ b/db/migrate/20170203163317_create_poll_null_results.rb @@ -0,0 +1,18 @@ +class CreatePollNullResults < ActiveRecord::Migration + def change + create_table :poll_null_results do |t| + t.integer :author_id + t.integer :amount + t.string :origin + t.date :date + t.integer :booth_assignment_id + t.integer :officer_assignment_id + t.text :amount_log, default: "" + t.text :officer_assignment_id_log, default: "" + t.text :author_id_log, default: "" + end + + add_index :poll_null_results, :officer_assignment_id + add_index :poll_null_results, :booth_assignment_id + end +end diff --git a/db/migrate/20170208110146_add_officer_id_to_failed_census_calls.rb b/db/migrate/20170208110146_add_officer_id_to_failed_census_calls.rb new file mode 100644 index 000000000..6ce289bec --- /dev/null +++ b/db/migrate/20170208110146_add_officer_id_to_failed_census_calls.rb @@ -0,0 +1,6 @@ +class AddOfficerIdToFailedCensusCalls < ActiveRecord::Migration + def change + add_column :failed_census_calls, :poll_officer_id, :integer, index: true + add_foreign_key :failed_census_calls, :poll_officers + end +end diff --git a/db/migrate/20170208111639_add_failed_census_calls_count_to_poll_officers.rb b/db/migrate/20170208111639_add_failed_census_calls_count_to_poll_officers.rb new file mode 100644 index 000000000..664c51749 --- /dev/null +++ b/db/migrate/20170208111639_add_failed_census_calls_count_to_poll_officers.rb @@ -0,0 +1,5 @@ +class AddFailedCensusCallsCountToPollOfficers < ActiveRecord::Migration + def change + add_column :poll_officers, :failed_census_calls_count, :integer, default: 0 + end +end diff --git a/db/migrate/20170208112814_add_year_of_birth_to_failed_census_calls.rb b/db/migrate/20170208112814_add_year_of_birth_to_failed_census_calls.rb new file mode 100644 index 000000000..a3fb5c96a --- /dev/null +++ b/db/migrate/20170208112814_add_year_of_birth_to_failed_census_calls.rb @@ -0,0 +1,5 @@ +class AddYearOfBirthToFailedCensusCalls < ActiveRecord::Migration + def change + add_column :failed_census_calls, :year_of_birth, :integer + end +end diff --git a/db/migrate/20170208114548_add_user_data_log_to_poll_officer_assignments.rb b/db/migrate/20170208114548_add_user_data_log_to_poll_officer_assignments.rb new file mode 100644 index 000000000..93c2efd8b --- /dev/null +++ b/db/migrate/20170208114548_add_user_data_log_to_poll_officer_assignments.rb @@ -0,0 +1,5 @@ +class AddUserDataLogToPollOfficerAssignments < ActiveRecord::Migration + def change + add_column :poll_officer_assignments, :user_data_log, :string, default: "" + end +end diff --git a/db/migrate/20170208160130_add_former_users_data_log_to_users.rb b/db/migrate/20170208160130_add_former_users_data_log_to_users.rb new file mode 100644 index 000000000..2cd3ca956 --- /dev/null +++ b/db/migrate/20170208160130_add_former_users_data_log_to_users.rb @@ -0,0 +1,5 @@ +class AddFormerUsersDataLogToUsers < ActiveRecord::Migration + def change + add_column :users, :former_users_data_log, :text, default: "" + end +end diff --git a/db/migrate/20170212123435_add_polls_related_indexes.rb b/db/migrate/20170212123435_add_polls_related_indexes.rb new file mode 100644 index 000000000..f1a667432 --- /dev/null +++ b/db/migrate/20170212123435_add_polls_related_indexes.rb @@ -0,0 +1,36 @@ +class AddPollsRelatedIndexes < ActiveRecord::Migration + def change + + add_index :poll_booth_assignments, :booth_id + add_index :poll_booth_assignments, :poll_id + + add_index :poll_final_recounts, :officer_assignment_id + + add_index :poll_officer_assignments, :booth_assignment_id + add_index :poll_officer_assignments, :officer_id + add_index :poll_officer_assignments, [:officer_id, :date] + + add_index :poll_officers, :user_id + + add_index :poll_voters, :booth_assignment_id + add_index :poll_voters, :officer_assignment_id + + add_index :polls, [:starts_at, :ends_at] + + + add_foreign_key :poll_answers, :poll_questions, column: :question_id + add_foreign_key :poll_booth_assignments, :polls + add_foreign_key :poll_final_recounts, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_final_recounts, :poll_officer_assignments, column: :officer_assignment_id + add_foreign_key :poll_null_results, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_null_results, :poll_officer_assignments, column: :officer_assignment_id + add_foreign_key :poll_white_results, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_white_results, :poll_officer_assignments, column: :officer_assignment_id + add_foreign_key :poll_officer_assignments, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_partial_results, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_partial_results, :poll_officer_assignments, column: :officer_assignment_id + add_foreign_key :poll_voters, :polls + add_foreign_key :poll_recounts, :poll_booth_assignments, column: :booth_assignment_id + add_foreign_key :poll_recounts, :poll_officer_assignments, column: :officer_assignment_id + end +end diff --git a/db/migrate/20170316174351_create_site_customization_pages.rb b/db/migrate/20170316174351_create_site_customization_pages.rb new file mode 100644 index 000000000..3c4fbe846 --- /dev/null +++ b/db/migrate/20170316174351_create_site_customization_pages.rb @@ -0,0 +1,15 @@ +class CreateSiteCustomizationPages < ActiveRecord::Migration + def change + create_table :site_customization_pages do |t| + t.string :slug, null: false + t.string :title, null: false + t.string :subtitle + t.text :content + t.boolean :more_info_flag + t.boolean :print_content_flag + t.string :status, default: 'draft' + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20170322145702_create_site_customization_images.rb b/db/migrate/20170322145702_create_site_customization_images.rb new file mode 100644 index 000000000..4f980b566 --- /dev/null +++ b/db/migrate/20170322145702_create_site_customization_images.rb @@ -0,0 +1,11 @@ +class CreateSiteCustomizationImages < ActiveRecord::Migration + def change + create_table :site_customization_images do |t| + t.string :name, null: false + t.attachment :image + t.timestamps null: false + end + + add_index :site_customization_images, :name, unique: true + end +end diff --git a/db/migrate/20170324101716_create_site_customization_content_blocks.rb b/db/migrate/20170324101716_create_site_customization_content_blocks.rb new file mode 100644 index 000000000..d0e4c3822 --- /dev/null +++ b/db/migrate/20170324101716_create_site_customization_content_blocks.rb @@ -0,0 +1,13 @@ +class CreateSiteCustomizationContentBlocks < ActiveRecord::Migration + def change + create_table :site_customization_content_blocks do |t| + t.string :name + t.string :locale + t.text :body + + t.timestamps null: false + end + + add_index :site_customization_content_blocks, [:name, :locale], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 20744daeb..d54a8f506 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: 20170114154421) do +ActiveRecord::Schema.define(version: 20170324101716) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -283,6 +283,8 @@ ActiveRecord::Schema.define(version: 20170114154421) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "district_code" + t.integer "poll_officer_id" + t.integer "year_of_birth" end add_index "failed_census_calls", ["user_id"], name: "index_failed_census_calls_on_user_id", using: :btree @@ -308,6 +310,14 @@ ActiveRecord::Schema.define(version: 20170114154421) do t.string "census_code" end + create_table "geozones_polls", force: :cascade do |t| + t.integer "geozone_id" + t.integer "poll_id" + end + + add_index "geozones_polls", ["geozone_id"], name: "index_geozones_polls_on_geozone_id", using: :btree + add_index "geozones_polls", ["poll_id"], name: "index_geozones_polls_on_poll_id", using: :btree + create_table "identities", force: :cascade do |t| t.integer "user_id" t.string "provider" @@ -367,6 +377,184 @@ ActiveRecord::Schema.define(version: 20170114154421) do add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree + create_table "poll_answers", force: :cascade do |t| + t.integer "question_id" + t.integer "author_id" + t.string "answer" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "poll_answers", ["author_id"], name: "index_poll_answers_on_author_id", 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 + + create_table "poll_booth_assignments", force: :cascade do |t| + t.integer "booth_id" + t.integer "poll_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "poll_booth_assignments", ["booth_id"], name: "index_poll_booth_assignments_on_booth_id", using: :btree + add_index "poll_booth_assignments", ["poll_id"], name: "index_poll_booth_assignments_on_poll_id", using: :btree + + create_table "poll_booths", force: :cascade do |t| + t.string "name" + t.string "location" + end + + create_table "poll_final_recounts", force: :cascade do |t| + t.integer "booth_assignment_id" + t.integer "officer_assignment_id" + t.integer "count" + t.text "count_log", default: "" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "officer_assignment_id_log", default: "" + 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 + add_index "poll_final_recounts", ["officer_assignment_id"], name: "index_poll_final_recounts_on_officer_assignment_id", using: :btree + + create_table "poll_null_results", force: :cascade do |t| + t.integer "author_id" + t.integer "amount" + t.string "origin" + t.date "date" + t.integer "booth_assignment_id" + t.integer "officer_assignment_id" + t.text "amount_log", default: "" + t.text "officer_assignment_id_log", default: "" + t.text "author_id_log", default: "" + end + + add_index "poll_null_results", ["booth_assignment_id"], name: "index_poll_null_results_on_booth_assignment_id", using: :btree + add_index "poll_null_results", ["officer_assignment_id"], name: "index_poll_null_results_on_officer_assignment_id", using: :btree + + create_table "poll_officer_assignments", force: :cascade do |t| + t.integer "booth_assignment_id" + t.integer "officer_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.date "date", null: false + t.boolean "final", default: false + t.string "user_data_log", default: "" + end + + add_index "poll_officer_assignments", ["booth_assignment_id"], name: "index_poll_officer_assignments_on_booth_assignment_id", using: :btree + add_index "poll_officer_assignments", ["officer_id", "date"], name: "index_poll_officer_assignments_on_officer_id_and_date", using: :btree + add_index "poll_officer_assignments", ["officer_id"], name: "index_poll_officer_assignments_on_officer_id", using: :btree + + create_table "poll_officers", force: :cascade do |t| + t.integer "user_id" + t.integer "failed_census_calls_count", default: 0 + end + + add_index "poll_officers", ["user_id"], name: "index_poll_officers_on_user_id", using: :btree + + create_table "poll_partial_results", force: :cascade do |t| + t.integer "question_id" + t.integer "author_id" + t.string "answer" + t.integer "amount" + t.string "origin" + t.date "date" + t.integer "booth_assignment_id" + t.integer "officer_assignment_id" + t.text "amount_log", default: "" + t.text "officer_assignment_id_log", default: "" + t.text "author_id_log", default: "" + end + + add_index "poll_partial_results", ["answer"], name: "index_poll_partial_results_on_answer", using: :btree + add_index "poll_partial_results", ["author_id"], name: "index_poll_partial_results_on_author_id", using: :btree + add_index "poll_partial_results", ["booth_assignment_id", "date"], name: "index_poll_partial_results_on_booth_assignment_id_and_date", using: :btree + add_index "poll_partial_results", ["origin"], name: "index_poll_partial_results_on_origin", using: :btree + add_index "poll_partial_results", ["question_id"], name: "index_poll_partial_results_on_question_id", using: :btree + + create_table "poll_questions", force: :cascade do |t| + t.integer "proposal_id" + t.integer "poll_id" + t.integer "author_id" + t.string "author_visible_name" + t.string "title" + t.string "valid_answers" + t.text "description" + t.integer "comments_count" + t.datetime "hidden_at" + t.datetime "created_at" + t.datetime "updated_at" + t.tsvector "tsv" + end + + add_index "poll_questions", ["author_id"], name: "index_poll_questions_on_author_id", using: :btree + add_index "poll_questions", ["poll_id"], name: "index_poll_questions_on_poll_id", using: :btree + add_index "poll_questions", ["proposal_id"], name: "index_poll_questions_on_proposal_id", using: :btree + add_index "poll_questions", ["tsv"], name: "index_poll_questions_on_tsv", using: :gin + + create_table "poll_recounts", force: :cascade do |t| + t.integer "booth_assignment_id" + t.integer "officer_assignment_id" + t.integer "count" + t.text "count_log", default: "" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.date "date", null: false + t.text "officer_assignment_id_log", default: "" + end + + add_index "poll_recounts", ["booth_assignment_id"], name: "index_poll_recounts_on_booth_assignment_id", using: :btree + add_index "poll_recounts", ["officer_assignment_id"], name: "index_poll_recounts_on_officer_assignment_id", using: :btree + + create_table "poll_voters", force: :cascade do |t| + t.string "document_number" + t.string "document_type" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "poll_id", null: false + t.integer "booth_assignment_id" + t.integer "age" + t.string "gender" + t.integer "geozone_id" + t.integer "answer_id" + t.integer "officer_assignment_id" + t.integer "user_id" + end + + add_index "poll_voters", ["booth_assignment_id"], name: "index_poll_voters_on_booth_assignment_id", using: :btree + add_index "poll_voters", ["document_number"], name: "index_poll_voters_on_document_number", using: :btree + add_index "poll_voters", ["officer_assignment_id"], name: "index_poll_voters_on_officer_assignment_id", using: :btree + add_index "poll_voters", ["poll_id", "document_number", "document_type"], name: "doc_by_poll", using: :btree + add_index "poll_voters", ["poll_id"], name: "index_poll_voters_on_poll_id", using: :btree + add_index "poll_voters", ["user_id"], name: "index_poll_voters_on_user_id", using: :btree + + create_table "poll_white_results", force: :cascade do |t| + t.integer "author_id" + t.integer "amount" + t.string "origin" + t.date "date" + t.integer "booth_assignment_id" + t.integer "officer_assignment_id" + t.text "amount_log", default: "" + t.text "officer_assignment_id_log", default: "" + t.text "author_id_log", default: "" + end + + add_index "poll_white_results", ["booth_assignment_id"], name: "index_poll_white_results_on_booth_assignment_id", using: :btree + add_index "poll_white_results", ["officer_assignment_id"], name: "index_poll_white_results_on_officer_assignment_id", using: :btree + + create_table "polls", force: :cascade do |t| + t.string "name" + t.datetime "starts_at" + t.datetime "ends_at" + t.boolean "published", default: false + t.boolean "geozone_restricted", default: false + end + + add_index "polls", ["starts_at", "ends_at"], name: "index_polls_on_starts_at_and_ends_at", using: :btree + create_table "proposal_notifications", force: :cascade do |t| t.string "title" t.text "body" @@ -441,6 +629,40 @@ ActiveRecord::Schema.define(version: 20170114154421) do t.datetime "updated_at" end + create_table "site_customization_content_blocks", force: :cascade do |t| + t.string "name" + t.string "locale" + t.text "body" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "site_customization_content_blocks", ["name", "locale"], name: "index_site_customization_content_blocks_on_name_and_locale", unique: true, using: :btree + + create_table "site_customization_images", force: :cascade do |t| + t.string "name", null: false + t.string "image_file_name" + t.string "image_content_type" + t.integer "image_file_size" + t.datetime "image_updated_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "site_customization_images", ["name"], name: "index_site_customization_images_on_name", unique: true, using: :btree + + create_table "site_customization_pages", force: :cascade do |t| + t.string "slug", null: false + t.string "title", null: false + t.string "subtitle" + t.text "content" + t.boolean "more_info_flag" + t.boolean "print_content_flag" + t.string "status", default: "draft" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "spending_proposals", force: :cascade do |t| t.string "title" t.text "description" @@ -580,9 +802,10 @@ ActiveRecord::Schema.define(version: 20170114154421) do t.boolean "email_digest", default: true t.boolean "email_on_direct_message", default: true t.boolean "official_position_badge", default: false - t.datetime "password_changed_at", default: '2016-11-02 13:51:14', null: false + t.datetime "password_changed_at", default: '2016-11-23 10:59:20', null: false t.boolean "created_from_signature", default: false t.integer "failed_email_digests_count", default: 0 + t.text "former_users_data_log", default: "" end add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree @@ -673,14 +896,36 @@ ActiveRecord::Schema.define(version: 20170114154421) do add_foreign_key "administrators", "users" add_foreign_key "annotations", "legislations" add_foreign_key "annotations", "users" + add_foreign_key "failed_census_calls", "poll_officers" add_foreign_key "failed_census_calls", "users" add_foreign_key "flags", "users" + add_foreign_key "geozones_polls", "geozones" + add_foreign_key "geozones_polls", "polls" add_foreign_key "identities", "users" add_foreign_key "locks", "users" add_foreign_key "managers", "users" add_foreign_key "moderators", "users" add_foreign_key "notifications", "users" add_foreign_key "organizations", "users" + add_foreign_key "poll_answers", "poll_questions", column: "question_id" + add_foreign_key "poll_booth_assignments", "polls" + add_foreign_key "poll_final_recounts", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_final_recounts", "poll_officer_assignments", column: "officer_assignment_id" + add_foreign_key "poll_null_results", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_null_results", "poll_officer_assignments", column: "officer_assignment_id" + add_foreign_key "poll_officer_assignments", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_partial_results", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_partial_results", "poll_officer_assignments", column: "officer_assignment_id" + add_foreign_key "poll_partial_results", "poll_questions", column: "question_id" + add_foreign_key "poll_partial_results", "users", column: "author_id" + add_foreign_key "poll_questions", "polls" + add_foreign_key "poll_questions", "proposals" + add_foreign_key "poll_questions", "users", column: "author_id" + add_foreign_key "poll_recounts", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_recounts", "poll_officer_assignments", column: "officer_assignment_id" + add_foreign_key "poll_voters", "polls" + add_foreign_key "poll_white_results", "poll_booth_assignments", column: "booth_assignment_id" + add_foreign_key "poll_white_results", "poll_officer_assignments", column: "officer_assignment_id" add_foreign_key "users", "geozones" add_foreign_key "valuators", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index 41261d9d7..0f1a4f7a3 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -38,15 +38,21 @@ Setting["months_to_archive_proposals"] = 12 # Emails under the domain's subdomains will also be included Setting["email_domain_for_officials"] = '' -# Code to be included at the top (header) of every page (useful for tracking) -Setting['per_page_code'] = '' +# Code to be included at the top (inside ) of every page (useful for tracking) +Setting['per_page_code_head'] = '' + +# Code to be included at the top (inside ) of every page +Setting['per_page_code_body'] = '' # Social settings Setting["twitter_handle"] = nil Setting["twitter_hashtag"] = nil Setting["facebook_handle"] = nil Setting["youtube_handle"] = nil +Setting["telegram_handle"] = nil Setting["blog_url"] = nil +Setting["transparency_url"] = nil +Setting["opendata_url"] = "/opendata" # Public-facing URL of the app. Setting["url"] = "http://example.com" @@ -64,6 +70,7 @@ Setting["meta_keywords"] = nil # Feature flags Setting['feature.debates'] = true Setting['feature.spending_proposals'] = nil +Setting['feature.polls'] = true Setting['feature.twitter_login'] = true Setting['feature.facebook_login'] = true Setting['feature.google_login'] = true diff --git a/lib/age.rb b/lib/age.rb new file mode 100644 index 000000000..c6ae6fe18 --- /dev/null +++ b/lib/age.rb @@ -0,0 +1,7 @@ +module Age + def self.in_years(dob, now = Time.now.utc.to_date) + return nil unless dob.present? + # reference: http://stackoverflow.com/questions/819263/get-persons-age-in-ruby#comment21200772_819263 + now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1) + end +end diff --git a/lib/census_api.rb b/lib/census_api.rb index 7f88557f2..eadfc549e 100644 --- a/lib/census_api.rb +++ b/lib/census_api.rb @@ -61,6 +61,10 @@ class CensusApi end end + def name + "#{data[:datos_habitante][:item][:nombre]} #{data[:datos_habitante][:item][:apellido1]}" + end + private def data diff --git a/spec/controllers/pages_controller_spec.rb b/spec/controllers/pages_controller_spec.rb index eed930a57..b72bf032e 100644 --- a/spec/controllers/pages_controller_spec.rb +++ b/spec/controllers/pages_controller_spec.rb @@ -22,38 +22,37 @@ describe PagesController do get :show, id: :census_terms expect(response).to be_ok end - end - describe 'Provisional pages' do - it 'should include a opendata page' do - get :show, id: :opendata + it 'should include a accessibility page' do + get :show, id: :accessibility expect(response).to be_ok end end - describe 'Info pages' do - it 'should include a how_it_works page' do - get :show, id: :how_it_works + describe 'More info pages' do + + it 'should include a more info page' do + get :show, id: 'more_info/index' expect(response).to be_ok end it 'should include a how_to_use page' do - get :show, id: :how_to_use + get :show, id: 'more_info/how_to_use/index' expect(response).to be_ok end - it 'should include a more_information page' do - get :show, id: :more_information + it 'should include a faq page' do + get :show, id: 'more_info/faq/index' expect(response).to be_ok end - it 'should include a participation page' do - get :show, id: :participation + it 'should include a participation facts page' do + get :show, id: 'more_info/participation/facts' expect(response).to be_ok end - it 'should include a accessibility page' do - get :show, id: :accessibility + it 'should include a participation world page' do + get :show, id: 'more_info/participation/world' expect(response).to be_ok end end diff --git a/spec/factories.rb b/spec/factories.rb index e058adf15..b295fd60c 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -22,6 +22,9 @@ FactoryGirl.define do sms_confirmation_code "1234" document_type "1" document_number + date_of_birth Date.new(1980, 12, 31) + gender "female" + geozone end trait :level_three do @@ -37,6 +40,7 @@ FactoryGirl.define do trait :with_confirmed_hide do confirmed_hide_at Time.current end + end factory :identity do @@ -181,6 +185,10 @@ FactoryGirl.define do 4.times { create(:vote, votable: debate) } end end + + trait :successful do + cached_votes_up { Proposal.votes_needed_for_success + 100 } + end end factory :spending_proposal do @@ -359,6 +367,127 @@ FactoryGirl.define do user end + factory :poll_officer, class: 'Poll::Officer' do + user + end + + factory :poll do + sequence(:name) { |n| "Poll #{n}" } + + starts_at { 1.month.ago } + ends_at { 1.month.from_now } + + trait :incoming do + starts_at { 2.days.from_now } + ends_at { 1.month.from_now } + end + + trait :expired do + starts_at { 1.month.ago } + ends_at { 15.days.ago } + end + + trait :published do + published true + end + end + + factory :poll_question, class: 'Poll::Question' do + poll + association :author, factory: :user + sequence(:title) { |n| "Question title #{n}" } + sequence(:description) { |n| "Question description #{n}" } + valid_answers { Faker::Lorem.words(3).join(', ') } + end + + factory :poll_booth, class: 'Poll::Booth' do + sequence(:name) { |n| "Booth #{n}" } + sequence(:location) { |n| "Street #{n}" } + end + + factory :poll_booth_assignment, class: 'Poll::BoothAssignment' do + poll + association :booth, factory: :poll_booth + end + + factory :poll_officer_assignment, class: 'Poll::OfficerAssignment' 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 + association :officer_assignment, factory: :poll_officer_assignment + 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_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 + + trait :from_booth do + association :booth_assignment, factory: :poll_booth_assignment + end + + trait :valid_document do + document_type "1" + document_number "12345678Z" + end + + trait :invalid_document do + document_type "1" + document_number "99999999A" + end + end + + factory :poll_answer, class: 'Poll::Answer' do + association :question, factory: :poll_question + association :author, factory: [:user, :level_two] + answer { question.valid_answers.sample } + end + + factory :poll_partial_result, class: 'Poll::PartialResult' do + association :question, factory: :poll_question + association :author, factory: :user + origin { 'web' } + answer { question.valid_answers.sample } + end + + factory :poll_white_result, class: 'Poll::WhiteResult' do + association :author, factory: :user + origin { 'web' } + end + + factory :poll_null_result, class: 'Poll::NullResult' do + association :author, factory: :user + origin { 'web' } + end + + factory :officing_residence, class: 'Officing::Residence' do + user + association :officer, factory: :poll_officer + document_number + document_type "1" + year_of_birth "1980" + + trait :invalid do + year_of_birth Time.current.year + end + end + factory :organization do user responsible_name "Johnny Utah" @@ -415,6 +544,10 @@ FactoryGirl.define do sequence(:name) { |n| "District #{n}" } sequence(:external_code) { |n| "#{n}" } sequence(:census_code) { |n| "#{n}" } + + trait :in_census do + census_code "01" + end end factory :banner do @@ -450,4 +583,28 @@ FactoryGirl.define do signature_sheet sequence(:document_number) { |n| "#{n}A" } end + + factory :site_customization_page, class: 'SiteCustomization::Page' do + slug "example-page" + title "Example page" + subtitle "About an example" + content "This page is about..." + more_info_flag false + print_content_flag false + status 'draft' + + trait :published do + status "published" + end + + trait :display_in_more_info do + more_info_flag true + end + end + + factory :site_customization_content_block, class: 'SiteCustomization::ContentBlock' do + name "top_links" + locale "en" + body "Some top links content" + end end diff --git a/spec/features/account_spec.rb b/spec/features/account_spec.rb index d1c547185..ef24226f1 100644 --- a/spec/features/account_spec.rb +++ b/spec/features/account_spec.rb @@ -112,8 +112,13 @@ feature 'Account' do end scenario 'Errors editing credentials' do - visit account_path + visit root_path + click_link 'My account' + + expect(current_path).to eq(account_path) + + expect(page).to have_link('Change my credentials') click_link 'Change my credentials' click_button 'Update' diff --git a/spec/features/admin/banners_spec.rb b/spec/features/admin/banners_spec.rb index 72814b3c3..70d9f7621 100644 --- a/spec/features/admin/banners_spec.rb +++ b/spec/features/admin/banners_spec.rb @@ -114,6 +114,7 @@ feature 'Admin banners magement' do visit admin_root_path within('#side_menu') do + click_link "Banners" click_link "Manage banners" end diff --git a/spec/features/admin/budgets_spec.rb b/spec/features/admin/budgets_spec.rb index 3b34878af..c93aebea1 100644 --- a/spec/features/admin/budgets_spec.rb +++ b/spec/features/admin/budgets_spec.rb @@ -84,7 +84,7 @@ feature 'Admin budgets' do fill_in 'budget_name', with: 'M30 - Summer campaign' fill_in 'budget_description_accepting', with: 'Budgeting for summer 2017 maintenance and improvements of the road M-30' - select 'Accepting proposals', from: 'budget[phase]' + select 'Accepting projects', from: 'budget[phase]' click_button 'Create Participatory budget' diff --git a/spec/features/admin/feature_flags_spec.rb b/spec/features/admin/feature_flags_spec.rb index 18b92eed5..d4500d2f9 100644 --- a/spec/features/admin/feature_flags_spec.rb +++ b/spec/features/admin/feature_flags_spec.rb @@ -31,6 +31,7 @@ feature 'Admin feature flags' do visit admin_root_path within('#side_menu') do + expect(page).not_to have_link "Budgets" expect(page).not_to have_link "Spending proposals" end @@ -45,6 +46,7 @@ feature 'Admin feature flags' do visit admin_root_path within('#side_menu') do + expect(page).not_to have_link "Budgets" expect(page).not_to have_link "Spending proposals" end diff --git a/spec/features/admin/poll/booth_assigments_spec.rb b/spec/features/admin/poll/booth_assigments_spec.rb new file mode 100644 index 000000000..7a07eab82 --- /dev/null +++ b/spec/features/admin/poll/booth_assigments_spec.rb @@ -0,0 +1,181 @@ +require 'rails_helper' + +feature 'Admin booths assignments' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario 'Assign booth to poll', :js do + poll = create(:poll) + booth = create(:poll_booth) + + visit admin_poll_path(poll) + within('#poll-resources') do + click_link 'Booths (0)' + end + + expect(page).to have_content 'There are no booths assigned to this poll.' + + fill_in 'search-booths', with: booth.name + click_button 'Search' + expect(page).to have_content(booth.name) + + within('#search-booths-results') do + click_link 'Assign booth' + end + + expect(page).to have_content 'Booth assigned' + + visit admin_poll_path(poll) + within('#poll-resources') do + click_link 'Booths (1)' + end + + expect(page).to_not have_content 'There are no booths assigned to this poll.' + expect(page).to have_content booth.name + end + + scenario 'Remove booth from poll', :js do + poll = create(:poll) + booth = create(:poll_booth) + assignment = create(:poll_booth_assignment, poll: poll, booth: booth) + + visit admin_poll_path(poll) + within('#poll-resources') do + click_link 'Booths (1)' + end + + expect(page).to_not have_content 'There are no booths assigned to this poll.' + expect(page).to have_content booth.name + + within("#poll_booth_assignment_#{assignment.id}") do + click_link 'Remove booth from poll' + end + + expect(page).to have_content 'Booth not assigned anymore' + + visit admin_poll_path(poll) + within('#poll-resources') do + click_link 'Booths (0)' + end + + expect(page).to have_content 'There are no booths assigned to this poll.' + expect(page).to_not have_content booth.name + end + + feature 'Show' do + scenario 'Lists all assigned poll oficers' do + poll = create(:poll) + booth = create(:poll_booth) + booth_assignment = create(:poll_booth_assignment, poll: poll, booth: booth) + officer_assignment = create(:poll_officer_assignment, booth_assignment: booth_assignment) + officer = officer_assignment.officer + + booth_assignment_2 = create(:poll_booth_assignment, poll: poll) + officer_assignment_2 = create(:poll_officer_assignment, booth_assignment: booth_assignment_2) + officer_2 = officer_assignment_2.officer + + visit admin_poll_path(poll) + click_link 'Booths (2)' + + within('#assigned_booths_list') { click_link booth.name } + + click_link 'Officers' + within('#officers_list') do + expect(page).to have_content officer.name + expect(page).to_not have_content officer_2.name + end + end + + scenario 'Lists all recounts for the booth assignment' do + poll = create(:poll, starts_at: 2.weeks.ago, ends_at: 1.week.ago) + booth = create(:poll_booth) + booth_assignment = create(:poll_booth_assignment, poll: poll, booth: booth) + officer_assignment_1 = create(:poll_officer_assignment, booth_assignment: booth_assignment, date: poll.starts_at) + officer_assignment_2 = create(:poll_officer_assignment, booth_assignment: booth_assignment, date: poll.ends_at) + final_officer_assignment = create(:poll_officer_assignment, :final, booth_assignment: booth_assignment, date: poll.ends_at) + + recount_1 = create(:poll_recount, + booth_assignment: booth_assignment, + officer_assignment: officer_assignment_1, + date: officer_assignment_1.date, + count: 33) + recount_2 = create(:poll_recount, + booth_assignment: booth_assignment, + officer_assignment: officer_assignment_2, + date: officer_assignment_2.date, + count: 78) + final_recount = create(:poll_final_recount, + booth_assignment: booth_assignment, + officer_assignment: final_officer_assignment, + date: final_officer_assignment.date, + count: 5678) + + booth_assignment_2 = create(:poll_booth_assignment, poll: poll) + other_recount = create(:poll_recount, booth_assignment: booth_assignment_2, count: 100) + + visit admin_poll_path(poll) + click_link 'Booths (2)' + + within('#assigned_booths_list') { click_link booth.name } + + click_link 'Recounts' + within('#recounts_list') do + expect(page).to_not have_content other_recount.count + + within("#recounting_#{recount_1.date.strftime('%Y%m%d')}") do + expect(page).to have_content recount_1.count + end + + within("#recounting_#{recount_2.date.strftime('%Y%m%d')}") do + expect(page).to have_content recount_2.count + end + + within("#recounting_#{final_recount.date.strftime('%Y%m%d')}") do + expect(page).to have_content final_recount.count + end + + end + end + + scenario 'Marks recount values with count-errors' do + poll = create(:poll) + booth = create(:poll_booth) + booth_assignment = create(:poll_booth_assignment, poll: poll, booth: booth) + today = Time.current.to_date + officer_assignment = create(:poll_officer_assignment, booth_assignment: booth_assignment, date: today) + + recount = create(:poll_recount, + booth_assignment: booth_assignment, + officer_assignment: officer_assignment, + date: officer_assignment.date, + count: 1) + + visit admin_poll_booth_assignment_path(poll, booth_assignment) + click_link 'Recounts' + + within('#recounts_list') do + expect(page).to have_css("#recounting_#{recount.date.strftime('%Y%m%d')} td.count-error") + within("#recounting_#{recount.date.strftime('%Y%m%d')}") do + expect(page).to have_content recount.count + expect(page).to have_content 0 + end + end + + create(:poll_voter, :valid_document, poll: poll, booth_assignment: booth_assignment) + + visit admin_poll_booth_assignment_path(poll, booth_assignment) + click_link 'Recounts' + + within('#recounts_list') do + expect(page).to_not have_css('.count-error') + within("#recounting_#{recount.date.strftime('%Y%m%d')}") do + expect(page).to have_content(recount.count) + end + end + end + + end +end diff --git a/spec/features/admin/poll/booths_spec.rb b/spec/features/admin/poll/booths_spec.rb new file mode 100644 index 000000000..ec8c10d58 --- /dev/null +++ b/spec/features/admin/poll/booths_spec.rb @@ -0,0 +1,86 @@ +require 'rails_helper' + +feature 'Admin booths' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario 'Index empty' do + visit admin_root_path + + within('#side_menu') do + click_link "Booths location" + end + + expect(page).to have_content "There are no booths" + end + + scenario 'Index' do + 3.times { create(:poll_booth) } + + visit admin_root_path + + within('#side_menu') do + click_link "Booths location" + end + + booths = Poll::Booth.all + booths.each do |booth| + within("#booth_#{booth.id}") do + expect(page).to have_content booth.name + expect(page).to have_content booth.location + end + end + expect(page).to_not have_content "There are no booths" + end + + scenario 'Show' do + booth = create(:poll_booth) + + visit admin_booths_path + + expect(page).to have_content booth.name + expect(page).to have_content booth.location + end + + scenario "Create" do + visit admin_booths_path + click_link "Add booth" + + fill_in "poll_booth_name", with: "Upcoming booth" + fill_in "poll_booth_location", with: "39th Street, number 2, ground floor" + click_button "Create booth" + + expect(page).to have_content "Booth created successfully" + + visit admin_booths_path + expect(page).to have_content "Upcoming booth" + expect(page).to have_content "39th Street, number 2, ground floor" + end + + scenario "Edit" do + booth = create(:poll_booth) + + visit admin_booths_path + + within("#booth_#{booth.id}") do + click_link "Edit" + end + + fill_in "poll_booth_name", with: "Next booth" + fill_in "poll_booth_location", with: "40th Street, number 1, firts floor" + click_button "Update booth" + + expect(page).to have_content "Booth updated successfully" + + visit admin_booths_path + + within("#booth_#{booth.id}") do + expect(page).to have_content "Next booth" + expect(page).to have_content "40th Street, number 1, firts floor" + end + end + +end \ No newline at end of file diff --git a/spec/features/admin/poll/officer_assignments_spec.rb b/spec/features/admin/poll/officer_assignments_spec.rb new file mode 100644 index 000000000..6cbe43d14 --- /dev/null +++ b/spec/features/admin/poll/officer_assignments_spec.rb @@ -0,0 +1,97 @@ +require 'rails_helper' + +feature 'Admin officer assignments in poll' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario 'Assign officer to poll', :js do + booth_assignment = create(:poll_booth_assignment) + officer = create(:poll_officer) + + visit admin_poll_path(booth_assignment.poll) + within('#poll-resources') do + click_link 'Officers (0)' + end + + expect(page).to have_content 'There are no officers assigned to this poll' + + fill_in 'search-officers', with: officer.name + click_button 'Search' + + within('#search-officers-results') do + click_link 'Add shifts as officer' + end + + expect(page).to have_content 'This user has no officing shifts in this poll' + expect(page).to have_content officer.name + expect(page).to have_content booth_assignment.poll.name + + within('#officer_assignment_form') do + 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 + + expect(page).to have_content 'Officing shift added' + expect(page).to_not have_content 'This user has no officing shifts in this poll' + + visit admin_poll_path(booth_assignment.poll) + within('#poll-resources') do + click_link 'Officers (1)' + end + + expect(page).to_not have_content 'There are no officers in this poll' + expect(page).to have_content officer.name + expect(page).to have_content officer.email + end + + scenario 'Remove officer assignment from poll' do + officer_assignment = create(:poll_officer_assignment) + poll = officer_assignment.booth_assignment.poll + booth = officer_assignment.booth_assignment.booth + officer = officer_assignment.officer + + visit by_officer_admin_poll_officer_assignments_path(poll, officer_id: officer.id) + + expect(page).to_not have_content 'This user has no officing shifts in this poll' + within("#poll_officer_assignment_#{officer_assignment.id}") do + expect(page).to have_content booth.name + click_link 'Remove' + end + + expect(page).to have_content 'Officing shift removed' + expect(page).to have_content 'This user has no officing shifts in this poll' + end + + scenario 'Index view shows recounts info for officer' do + booth_assignment = create(:poll_booth_assignment) + poll = booth_assignment.poll + officer = create(:poll_officer) + officer_assignment = create(:poll_officer_assignment, + booth_assignment: booth_assignment, + officer: officer, + date: poll.starts_at) + final_officer_assignment = create(:poll_officer_assignment, :final, + booth_assignment: booth_assignment, + officer: officer, + date: poll.ends_at + 1.day) + recount = create(:poll_recount, + booth_assignment: booth_assignment, + officer_assignment: officer_assignment, + date: officer_assignment.date, + count: 77) + final_recount = create(:poll_final_recount, + booth_assignment: booth_assignment, + officer_assignment: final_officer_assignment, + date: poll.ends_at, + count: 9876) + + visit by_officer_admin_poll_officer_assignments_path(poll, officer_id: officer.id) + + within('#recount_list') { expect(page).to have_content('77') } + within('#final_recount_list') { expect(page).to have_content('9876') } + end +end \ No newline at end of file diff --git a/spec/features/admin/poll/officers_spec.rb b/spec/features/admin/poll/officers_spec.rb new file mode 100644 index 000000000..d9ea89638 --- /dev/null +++ b/spec/features/admin/poll/officers_spec.rb @@ -0,0 +1,36 @@ +require 'rails_helper' + +feature 'Admin poll officers' do + + background do + @admin = create(:administrator) + @user = create(:user, username: 'Pedro Jose Garcia') + @officer = create(:poll_officer) + login_as(@admin.user) + visit admin_officers_path + end + + scenario 'Index' do + expect(page).to have_content @officer.name + expect(page).to have_content @officer.email + expect(page).to_not have_content @user.name + end + + scenario 'Create', :js do + fill_in 'email', with: @user.email + click_button 'Search' + + expect(page).to have_content @user.name + click_link 'Add' + within("#officers") do + expect(page).to have_content @user.name + end + end + + scenario 'Delete' do + click_link 'Delete position' + + expect(page).to_not have_css '#officers' + end + +end \ No newline at end of file diff --git a/spec/features/admin/poll/polls_spec.rb b/spec/features/admin/poll/polls_spec.rb new file mode 100644 index 000000000..cb5a34265 --- /dev/null +++ b/spec/features/admin/poll/polls_spec.rb @@ -0,0 +1,366 @@ +require 'rails_helper' + +feature 'Admin polls' do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario 'Index empty', :js do + visit admin_root_path + + click_link "Polls" + within('#polls_menu') do + click_link "Polls" + end + + expect(page).to have_content "There are no polls" + end + + scenario 'Index', :js do + 3.times { create(:poll) } + + visit admin_root_path + + click_link "Polls" + within('#polls_menu') do + click_link "Polls" + end + + expect(page).to have_css ".poll", count: 3 + + polls = Poll.all + polls.each do |poll| + within("#poll_#{poll.id}") do + expect(page).to have_content poll.name + end + end + expect(page).to_not have_content "There are no polls" + end + + scenario 'Show' do + poll = create(:poll) + + visit admin_polls_path + click_link poll.name + + expect(page).to have_content poll.name + end + + scenario "Create" do + visit admin_polls_path + click_link "Create poll" + + start_date = 1.week.from_now + end_date = 2.weeks.from_now + + fill_in "poll_name", with: "Upcoming poll" + fill_in 'poll_starts_at', with: start_date.strftime("%d/%m/%Y") + fill_in 'poll_ends_at', with: end_date.strftime("%d/%m/%Y") + click_button "Create poll" + + expect(page).to have_content "Poll created successfully" + expect(page).to have_content "Upcoming poll" + expect(page).to have_content I18n.l(start_date.to_date) + expect(page).to have_content I18n.l(end_date.to_date) + end + + scenario "Edit" do + poll = create(:poll) + + visit admin_poll_path(poll) + click_link "Edit" + + end_date = 1.year.from_now + + fill_in "poll_name", with: "Next Poll" + fill_in 'poll_ends_at', with: end_date.strftime("%d/%m/%Y") + click_button "Update poll" + + expect(page).to have_content "Poll updated successfully" + expect(page).to have_content "Next Poll" + expect(page).to have_content I18n.l(end_date.to_date) + end + + scenario 'Edit from index' do + poll = create(:poll) + visit admin_polls_path + + within("#poll_#{poll.id}") do + click_link "Edit" + end + + expect(current_path).to eq(edit_admin_poll_path(poll)) + end + + context "Booths" do + + context "Poll show" do + + scenario "No booths" do + poll = create(:poll) + visit admin_poll_path(poll) + click_link "Booths (0)" + + expect(page).to have_content "There are no booths assigned to this poll." + end + + scenario "Booth list" do + poll = create(:poll) + 3.times { create(:poll_booth, polls: [poll]) } + + visit admin_poll_path(poll) + click_link "Booths (3)" + + expect(page).to have_css ".booth", count: 3 + + poll.booth_assignments.each do |ba| + within("#poll_booth_assignment_#{ba.id}") do + expect(page).to have_content ba.booth.name + expect(page).to have_content ba.booth.location + end + end + expect(page).to_not have_content "There are no booths assigned to this poll." + end + end + end + + context "Officers" do + + context "Poll show" do + + scenario "No officers", :js do + poll = create(:poll) + visit admin_poll_path(poll) + click_link "Officers (0)" + + expect(page).to have_content "There are no officers assigned to this poll" + end + + scenario "Officer list", :js do + poll = create(:poll) + booth = create(:poll_booth, polls: [poll]) + + booth.booth_assignments.each do |booth_assignment| + 3.times {create(:poll_officer_assignment, booth_assignment: booth_assignment) } + end + + visit admin_poll_path(poll) + + click_link "Officers (3)" + + expect(page).to have_css ".officer", count: 3 + + officers = Poll::Officer.all + officers.each do |officer| + within("#officer_#{officer.id}") do + expect(page).to have_content officer.name + expect(page).to have_content officer.email + end + end + expect(page).to_not have_content "There are no officers assigned to this poll" + end + end + end + + context "Questions" do + + context "Poll show" do + + scenario "Question list", :js do + poll = create(:poll) + question = create(:poll_question, poll: poll) + other_question = create(:poll_question) + + visit admin_poll_path(poll) + + expect(page).to have_content "Questions (1)" + expect(page).to have_content question.title + expect(page).to_not have_content other_question.title + expect(page).to_not have_content "There are no questions assigned to this poll" + end + + scenario 'Add question to poll', :js do + poll = create(:poll) + question = create(:poll_question, poll: nil, title: 'Should we rebuild the city?') + + visit admin_poll_path(poll) + + expect(page).to have_content 'Questions (0)' + expect(page).to have_content 'There are no questions assigned to this poll' + + fill_in 'search-questions', with: 'rebuild' + click_button 'Search' + + within('#search-questions-results') do + click_link 'Include question' + end + + expect(page).to have_content 'Question added to this poll' + + visit admin_poll_path(poll) + + expect(page).to have_content 'Questions (1)' + expect(page).to_not have_content 'There are no questions assigned to this poll' + expect(page).to have_content question.title + end + + scenario 'Remove question from poll', :js do + poll = create(:poll) + question = create(:poll_question, poll: poll) + + visit admin_poll_path(poll) + + expect(page).to have_content 'Questions (1)' + expect(page).to_not have_content 'There are no questions assigned to this poll' + expect(page).to have_content question.title + + within("#poll_question_#{question.id}") do + click_link 'Remove question from poll' + end + + expect(page).to have_content 'Question removed from this poll' + + visit admin_poll_path(poll) + + expect(page).to have_content 'Questions (0)' + expect(page).to have_content 'There are no questions assigned to this poll' + expect(page).to_not have_content question.title + end + + end + end + + context "Recounting" do + context "Poll show" do + scenario "No recounts", :js do + poll = create(:poll) + visit admin_poll_path(poll) + click_link "Recounting" + + expect(page).to have_content "There is nothing to be recounted" + end + + scenario "Recounts list", :js do + poll = create(:poll) + booth_assignment = create(:poll_booth_assignment, poll: poll) + booth_assignment_recounted = create(:poll_booth_assignment, poll: poll) + booth_assignment_final_recounted = create(:poll_booth_assignment, poll: poll) + + 3.times { |i| create(:poll_recount, + booth_assignment: booth_assignment, + date: poll.starts_at + i.days, + count: 33) } + + 3.times { |i| create(:poll_final_recount, + booth_assignment: booth_assignment, + date: poll.starts_at + i.days, + count: 21) } + + 2.times { create(:poll_voter, + booth_assignment: booth_assignment_final_recounted) } + + create(:poll_recount, + booth_assignment: booth_assignment_recounted, + date: poll.ends_at, + count: 777) + + create(:poll_final_recount, + booth_assignment: booth_assignment_final_recounted, + date: poll.ends_at, + count: 55555) + + visit admin_poll_path(poll) + + click_link "Recounting" + + expect(page).to have_css ".booth_recounts", count: 3 + + within("#poll_booth_assignment_#{booth_assignment.id}_recounts") do + expect(page).to have_content(booth_assignment.booth.name) + expect(page).to have_content('99') + expect(page).to have_content('63') + end + + within("#poll_booth_assignment_#{booth_assignment_recounted.id}_recounts") do + expect(page).to have_content(booth_assignment_recounted.booth.name) + expect(page).to have_content('777') + expect(page).to have_content('-') + end + + within("#poll_booth_assignment_#{booth_assignment_final_recounted.id}_recounts") do + expect(page).to have_content(booth_assignment_final_recounted.booth.name) + expect(page).to have_content('-') + expect(page).to have_content('55555') + expect(page).to have_content('2') + end + end + end + end + + context "Results" do + context "Poll show" do + scenario "No results", :js do + poll = create(:poll) + visit admin_poll_path(poll) + click_link "Results" + + expect(page).to have_content "There are no results" + end + + scenario "Results by answer", :js do + poll = create(:poll) + booth_assignment_1 = create(:poll_booth_assignment, poll: poll) + booth_assignment_2 = create(:poll_booth_assignment, poll: poll) + booth_assignment_3 = create(:poll_booth_assignment, poll: poll) + + question_1 = create(:poll_question, poll: poll, valid_answers: "Yes,No") + question_2 = create(:poll_question, poll: poll, valid_answers: "Today,Tomorrow") + + [booth_assignment_1, booth_assignment_2, booth_assignment_3].each do |ba| + create(:poll_partial_result, + booth_assignment: ba, + question: question_1, + answer: 'Yes', + amount: 11) + create(:poll_partial_result, + booth_assignment: ba, + question: question_2, + answer: 'Tomorrow', + amount: 5) + end + create(:poll_white_result, + booth_assignment: booth_assignment_1, + amount: 21) + create(:poll_null_result, + booth_assignment: booth_assignment_3, + amount: 44) + + visit admin_poll_path(poll) + + click_link "Results" + + expect(page).to have_content(question_1.title) + question_1.valid_answers.each_with_index do |answer, i| + within("#question_#{question_1.id}_#{i}_result") do + expect(page).to have_content(answer) + expect(page).to have_content([33, 0][i]) + end + end + + expect(page).to have_content(question_2.title) + question_2.valid_answers.each_with_index do |answer, i| + within("#question_#{question_2.id}_#{i}_result") do + expect(page).to have_content(answer) + expect(page).to have_content([0,15][i]) + end + end + + within('#white_results') { expect(page).to have_content('21') } + within('#null_results') { expect(page).to have_content('44') } + end + end + end + +end \ No newline at end of file diff --git a/spec/features/admin/poll/questions_spec.rb b/spec/features/admin/poll/questions_spec.rb new file mode 100644 index 000000000..2a1e100f0 --- /dev/null +++ b/spec/features/admin/poll/questions_spec.rb @@ -0,0 +1,116 @@ +require 'rails_helper' + +feature 'Admin poll questions' do + + background do + login_as(create(:administrator).user) + end + + scenario 'Index' do + question1 = create(:poll_question) + question2 = create(:poll_question) + + visit admin_questions_path + + expect(page).to have_content(question1.title) + expect(page).to have_content(question2.title) + end + + scenario 'Show' do + geozone = create(:geozone) + poll = create(:poll, geozone_restricted: true, geozone_ids: [geozone.id]) + question = create(:poll_question, poll: poll) + + visit admin_question_path(question) + + expect(page).to have_content(question.title) + expect(page).to have_content(question.description) + expect(page).to have_content(question.author.name) + expect(page).to have_content(question.valid_answers.join(" ")) + end + + scenario 'Create' do + poll = create(:poll, name: 'Movies') + title = "Star Wars: Episode IV - A New Hope" + description = %{ + During the battle, Rebel spies managed to steal secret plans to the Empire's ultimate weapon, the DEATH STAR, an armored space station with enough power to destroy an entire planet. + Pursued by the Empire's sinister agents, Princess Leia races home aboard her starship, custodian of the stolen plans that can save her people and restore freedom to the galaxy.... + } + + visit admin_questions_path + click_link "Create question" + + select 'Movies', from: 'poll_question_poll_id' + fill_in 'poll_question_title', with: title + fill_in 'poll_question_description', with: description + + click_button 'Save' + + expect(page).to have_content(title) + expect(page).to have_content(description) + end + + scenario 'Create from successful proposal index' do + poll = create(:poll, name: 'Proposals') + proposal = create(:proposal, :successful) + + visit proposals_path + click_link "Create question" + + expect(current_path).to eq(new_admin_question_path) + expect(page).to have_field('poll_question_title', with: proposal.title) + expect(page).to have_field('poll_question_description', with: proposal.description) + expect(page).to have_field('poll_question_valid_answers', with: "Yes, No") + + select 'Proposals', from: 'poll_question_poll_id' + + click_button 'Save' + + expect(page).to have_content(proposal.title) + expect(page).to have_content(proposal.description) + expect(page).to have_link(proposal.title, href: proposal_path(proposal)) + expect(page).to have_link(proposal.author.name, href: user_path(proposal.author)) + end + + pending "Create from successul proposal show" + + scenario 'Update' do + question1 = create(:poll_question) + + visit admin_questions_path + within("#poll_question_#{question1.id}") do + click_link "Edit" + end + + old_title = question1.title + new_title = "Potatoes are great and everyone should have one" + fill_in 'poll_question_title', with: new_title + + click_button 'Save' + + expect(page).to have_content "Changes saved" + expect(page).to have_content new_title + + visit admin_questions_path + + expect(page).to have_content(new_title) + expect(page).to_not have_content(old_title) + end + + scenario 'Destroy' do + question1 = create(:poll_question) + question2 = create(:poll_question) + + visit admin_questions_path + + within("#poll_question_#{question1.id}") do + click_link "Delete" + end + + expect(page).to_not have_content(question1.title) + expect(page).to have_content(question2.title) + end + + pending "Mark all city by default when creating a poll question from a successful proposal" + +end diff --git a/spec/features/admin/signature_sheets_spec.rb b/spec/features/admin/signature_sheets_spec.rb index 8243dd913..bcde7bfc0 100644 --- a/spec/features/admin/signature_sheets_spec.rb +++ b/spec/features/admin/signature_sheets_spec.rb @@ -19,20 +19,42 @@ feature 'Signature sheets' do end end - scenario 'Create' do - proposal = create(:proposal) - visit new_admin_signature_sheet_path + context 'Create' do + scenario 'Proposal' do + proposal = create(:proposal) + visit new_admin_signature_sheet_path - select "Citizen proposal", from: "signature_sheet_signable_type" - fill_in "signature_sheet_signable_id", with: proposal.id - fill_in "signature_sheet_document_numbers", with: "12345678Z, 99999999Z" - click_button "Create signature sheet" + select "Citizen proposal", from: "signature_sheet_signable_type" + fill_in "signature_sheet_signable_id", with: proposal.id + fill_in "signature_sheet_document_numbers", with: "12345678Z, 99999999Z" + click_button "Create signature sheet" - expect(page).to have_content "Signature sheet created successfully" + expect(page).to have_content "Signature sheet created successfully" - visit proposal_path(proposal) + visit proposal_path(proposal) + + expect(page).to have_content "1 support" + end + + scenario 'Budget Investment' do + investment = create(:budget_investment) + budget = investment.budget + budget.update(phase: 'selecting') + + visit new_admin_signature_sheet_path + + select "Investment", from: "signature_sheet_signable_type" + fill_in "signature_sheet_signable_id", with: investment.id + fill_in "signature_sheet_document_numbers", with: "12345678Z, 99999999Z" + click_button "Create signature sheet" + + expect(page).to have_content "Signature sheet created successfully" + + visit budget_investment_path(budget, investment) + + expect(page).to have_content "1 support" + end - expect(page).to have_content "1 support" end scenario 'Errors on create' do diff --git a/spec/features/admin/site_customization/content_blocks_spec.rb b/spec/features/admin/site_customization/content_blocks_spec.rb new file mode 100644 index 000000000..cc5dca34c --- /dev/null +++ b/spec/features/admin/site_customization/content_blocks_spec.rb @@ -0,0 +1,107 @@ +require 'rails_helper' + +feature "Admin custom content blocks" do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario "Index" do + block = create(:site_customization_content_block) + visit admin_site_customization_content_blocks_path + + expect(page).to have_content(block.name) + expect(page).to have_content(block.body) + end + + context "Create" do + scenario "Valid custom block" do + visit admin_root_path + + within("#side_menu") do + click_link "Custom content blocks" + end + + expect(page).to_not have_content "footer (es)" + + click_link "Create new content block" + + select "footer", from: "site_customization_content_block_name" + select "es", from: "site_customization_content_block_locale" + fill_in "site_customization_content_block_body", with: "Some custom content" + + click_button "Create Custom content block" + + expect(page).to have_content "footer (es)" + expect(page).to have_content "Some custom content" + end + + scenario "Invalid custom block" do + block = create(:site_customization_content_block) + + visit admin_root_path + + within("#side_menu") do + click_link "Custom content blocks" + end + + expect(page).to have_content "top_links (en)" + + click_link "Create new content block" + + select "top_links", from: "site_customization_content_block_name" + select "en", from: "site_customization_content_block_locale" + fill_in "site_customization_content_block_body", with: "Some custom content" + + click_button "Create Custom content block" + + expect(page).to have_content "Content block couldn't be created" + expect(page).to have_content "has already been taken" + end + end + + context "Update" do + scenario "Valid custom block" do + block = create(:site_customization_content_block) + visit admin_root_path + + within("#side_menu") do + click_link "Custom content blocks" + end + + click_link "top_links (en)" + + fill_in "site_customization_content_block_body", with: "Some other custom content" + click_button "Update Custom content block" + + expect(page).to have_content "Content block updated successfully" + expect(page).to have_content "Some other custom content" + end + end + + context "Delete" do + scenario "From index page" do + block = create(:site_customization_content_block) + visit admin_site_customization_content_blocks_path + + expect(page).to have_content("#{block.name} (#{block.locale})") + expect(page).to have_content(block.body) + + click_button "Delete" + + expect(page).to_not have_content("#{block.name} (#{block.locale})") + expect(page).to_not have_content(block.body) + end + + scenario "From edit page" do + block = create(:site_customization_content_block) + visit edit_admin_site_customization_content_block_path(block) + + click_button "Delete" + + expect(page).to_not have_content("#{block.name} (#{block.locale})") + expect(page).to_not have_content(block.body) + end + end +end diff --git a/spec/features/admin/site_customization/images_spec.rb b/spec/features/admin/site_customization/images_spec.rb new file mode 100644 index 000000000..094ac19bf --- /dev/null +++ b/spec/features/admin/site_customization/images_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' + +feature "Admin custom images" do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario "Upload valid image" do + visit admin_root_path + + within("#side_menu") do + click_link "Custom Images" + end + + within("tr.logo_header") do + attach_file "site_customization_image_image", "spec/fixtures/files/logo_header.png" + click_button "Update" + end + + expect(page).to have_css("tr.logo_header img[src*='logo_header.png']") + expect(page).to have_css("img[src*='logo_header.png']", count: 2) # one in the admin form an one in the page header + end + + scenario "Upload invalid image" do + visit admin_root_path + + within("#side_menu") do + click_link "Custom Images" + end + + within("tr.icon_home") do + attach_file "site_customization_image_image", "spec/fixtures/files/logo_header.png" + click_button "Update" + end + + expect(page).to have_content("Width must be 330px") + expect(page).to have_content("Height must be 240px") + end + + scenario "Delete image" do + visit admin_root_path + + within("#side_menu") do + click_link "Custom Images" + end + + within("tr.social-media-icon") do + attach_file "site_customization_image_image", "spec/fixtures/files/social-media-icon.png" + click_button "Update" + end + + expect(page).to have_css("img[src*='social-media-icon.png']") + + within("tr.social-media-icon") do + click_link "Delete" + end + + expect(page).to_not have_css("img[src*='social-media-icon.png']") + end +end diff --git a/spec/features/admin/site_customization/pages_spec.rb b/spec/features/admin/site_customization/pages_spec.rb new file mode 100644 index 000000000..5d03638d8 --- /dev/null +++ b/spec/features/admin/site_customization/pages_spec.rb @@ -0,0 +1,69 @@ +require 'rails_helper' + +feature "Admin custom pages" do + + background do + admin = create(:administrator) + login_as(admin.user) + end + + scenario "Index" do + custom_page = create(:site_customization_page) + visit admin_site_customization_pages_path + + expect(page).to have_content(custom_page.title) + end + + context "Create" do + scenario "Valid custom page" do + visit admin_root_path + + within("#side_menu") do + click_link "Custom Pages" + end + + expect(page).to_not have_content "An example custom page" + + click_link "Create new page" + + fill_in "site_customization_page_title", with: "An example custom page" + fill_in "site_customization_page_subtitle", with: "Page subtitle" + fill_in "site_customization_page_slug", with: "example-page" + fill_in "site_customization_page_content", with: "This page is about..." + + click_button "Create Custom page" + + expect(page).to have_content "An example custom page" + end + end + + context "Update" do + scenario "Valid custom page" do + create(:site_customization_page, title: "An example custom page") + visit admin_root_path + + within("#side_menu") do + click_link "Custom Pages" + end + + click_link "An example custom page" + + expect(page).to have_selector("h2", text: "An example custom page") + + fill_in "site_customization_page_title", with: "Another example custom page" + click_button "Update Custom page" + + expect(page).to have_content "Page updated successfully" + expect(page).to have_content "Another example custom page" + end + end + + scenario "Delete" do + custom_page = create(:site_customization_page, title: "An example custom page") + visit edit_admin_site_customization_page_path(custom_page) + + click_button "Delete" + + expect(page).to_not have_content("An example custom page") + end +end diff --git a/spec/features/admin/spending_proposals_spec.rb b/spec/features/admin/spending_proposals_spec.rb index 5539b5ead..953cdc4f6 100644 --- a/spec/features/admin/spending_proposals_spec.rb +++ b/spec/features/admin/spending_proposals_spec.rb @@ -93,11 +93,11 @@ feature 'Admin spending proposals' do expect(page).to have_button("Update") click_link("Back") expect(page).to_not have_button("Update") + expect(page).to have_link("Back") click_link("Back") expect(page).to_not have_link("Destroy the city") expect(page).to have_link("Realocate visitors") - end scenario "Filtering by admin", :js do @@ -135,8 +135,10 @@ feature 'Admin spending proposals' do click_link("Realocate visitors") click_link("Edit classification") expect(page).to have_button("Update") + expect(page).to have_link("Back") click_link("Back") expect(page).to_not have_button("Update") + expect(page).to have_link("Back") click_link("Back") expect(page).to have_content('There is 1 spending proposal') @@ -182,8 +184,10 @@ feature 'Admin spending proposals' do click_link("Realocate visitors") click_link("Edit classification") expect(page).to have_button("Update") + expect(page).to have_link("Back") click_link("Back") expect(page).to_not have_button("Update") + expect(page).to have_link("Back") click_link("Back") expect(page).to have_content('There is 1 spending proposal') diff --git a/spec/features/admin_spec.rb b/spec/features/admin_spec.rb index 161b01999..064021ab8 100644 --- a/spec/features/admin_spec.rb +++ b/spec/features/admin_spec.rb @@ -16,7 +16,7 @@ feature 'Admin' do expect(page).to have_content "You do not have permission to access this page" end - scenario 'Access as a moderator is not authorized' do + scenario 'Access as moderator is not authorized' do create(:moderator, user: user) login_as(user) visit admin_root_path @@ -26,7 +26,7 @@ feature 'Admin' do expect(page).to have_content "You do not have permission to access this page" end - scenario 'Access as a valuator is not authorized' do + scenario 'Access as valuator is not authorized' do create(:valuator, user: user) login_as(user) visit admin_root_path @@ -36,7 +36,7 @@ feature 'Admin' do expect(page).to have_content "You do not have permission to access this page" end - scenario 'Access as a manager is not authorized' do + scenario 'Access as manager is not authorized' do create(:manager, user: user) login_as(user) visit admin_root_path @@ -46,7 +46,17 @@ feature 'Admin' do expect(page).to have_content "You do not have permission to access this page" end - scenario 'Access as an administrator is authorized' do + scenario 'Access as poll officer is not authorized' do + create(:poll_officer, user: user) + login_as(user) + visit admin_root_path + + expect(current_path).not_to eq(admin_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + + scenario 'Access as administrator is authorized' do login_as(administrator) visit admin_root_path diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb index 1d812bd7e..5446e0a0a 100644 --- a/spec/features/budgets/investments_spec.rb +++ b/spec/features/budgets/investments_spec.rb @@ -405,7 +405,7 @@ feature 'Budget Investments' do budget.update(phase: "valuating") end - scenario "Sidebar in show should display supports text and supports" do + scenario "Sidebar in show should display support text and count" do investment = create(:budget_investment, :selected, budget: budget) create(:vote, votable: investment) @@ -417,8 +417,8 @@ feature 'Budget Investments' do end end - scenario "Index should display supports" do - investment = create(:budget_investment, :selected, budget: budget, heading: heading) + scenario "Index should display support count" do + investment = create(:budget_investment, budget: budget, heading: heading) create(:vote, votable: investment) visit budget_investments_path(budget, heading_id: heading.id) @@ -428,6 +428,18 @@ feature 'Budget Investments' do end end + scenario "Show should display support text and count" do + investment = create(:budget_investment, budget: budget, heading: heading) + create(:vote, votable: investment) + + visit budget_investment_path(budget, investment) + + within("#budget_investment_#{investment.id}") do + expect(page).to have_content "Supports" + expect(page).to have_content "1 support" + end + end + end context "Balloting Phase" do diff --git a/spec/features/comments/poll_questions_spec.rb b/spec/features/comments/poll_questions_spec.rb new file mode 100644 index 000000000..6e5cffe97 --- /dev/null +++ b/spec/features/comments/poll_questions_spec.rb @@ -0,0 +1 @@ +#refactor specs and test as one more commmentable \ No newline at end of file diff --git a/spec/features/debates_spec.rb b/spec/features/debates_spec.rb index f16f416bf..f6efceea2 100644 --- a/spec/features/debates_spec.rb +++ b/spec/features/debates_spec.rb @@ -54,7 +54,7 @@ feature 'Debates' do expect(page.html).to include "#{debate.title}" within('.social-share-button') do - expect(page.all('a').count).to be(3) # Twitter, Facebook, Google+ + expect(page.all('a').count).to be(4) # Twitter, Facebook, Google+, Telegram end end @@ -640,6 +640,28 @@ feature 'Debates' do end end + scenario "Search by custom invalid date range", :js do + debate1 = create(:debate, created_at: 2.years.ago) + debate2 = create(:debate, created_at: 3.days.ago) + debate3 = create(:debate, created_at: 9.days.ago) + + visit debates_path + + click_link "Advanced search" + select "Customized", from: "js-advanced-search-date-min" + fill_in "advanced_search_date_min", with: "9" + fill_in "advanced_search_date_max", with: "444444444" + click_button "Filter" + + within("#debates") do + expect(page).to have_css('.debate', count: 3) + + expect(page).to have_content(debate1.title) + expect(page).to have_content(debate2.title) + expect(page).to have_content(debate3.title) + end + end + scenario "Search by multiple filters", :js do ana = create :user, official_level: 1 john = create :user, official_level: 1 diff --git a/spec/features/home_spec.rb b/spec/features/home_spec.rb index 708b1c883..4deed36bb 100644 --- a/spec/features/home_spec.rb +++ b/spec/features/home_spec.rb @@ -43,7 +43,7 @@ feature "Home" do end def ie_alert_box_xpath - "/html/body/div[@class='wrapper']/comment()[contains(.,'ie-callout')]" + "/html/body/div[@class='wrapper ']/comment()[contains(.,'ie-callout')]" end end end diff --git a/spec/features/management/budget_investments_spec.rb b/spec/features/management/budget_investments_spec.rb index 5b3961306..c4d7a8797 100644 --- a/spec/features/management/budget_investments_spec.rb +++ b/spec/features/management/budget_investments_spec.rb @@ -137,6 +137,61 @@ feature 'Budget Investments' do end end + scenario "Listing - managers can see budgets in accepting phase" do + accepting_budget = create(:budget, phase: "accepting") + reviewing_budget = create(:budget, phase: "reviewing") + selecting_budget = create(:budget, phase: "selecting") + valuating_budget = create(:budget, phase: "valuating") + balloting_budget = create(:budget, phase: "balloting") + reviewing_ballots_budget = create(:budget, phase: "reviewing_ballots") + finished = create(:budget, phase: "finished") + + user = create(:user, :level_two) + login_managed_user(user) + + click_link "Create budget investment" + + expect(page).to have_content(accepting_budget.name) + + expect(page).to_not have_content(reviewing_budget.name) + expect(page).to_not have_content(selecting_budget.name) + expect(page).to_not have_content(valuating_budget.name) + expect(page).to_not have_content(balloting_budget.name) + expect(page).to_not have_content(reviewing_ballots_budget.name) + expect(page).to_not have_content(finished.name) + end + + scenario "Listing - admins can see budgets in accepting, reviewing and selecting phases" do + accepting_budget = create(:budget, phase: "accepting") + reviewing_budget = create(:budget, phase: "reviewing") + selecting_budget = create(:budget, phase: "selecting") + valuating_budget = create(:budget, phase: "valuating") + balloting_budget = create(:budget, phase: "balloting") + reviewing_ballots_budget = create(:budget, phase: "reviewing_ballots") + finished = create(:budget, phase: "finished") + + visit root_path + click_link "Sign out" + + admin = create(:administrator) + login_as(admin.user) + + user = create(:user, :level_two) + login_managed_user(user) + visit management_sign_in_path + + click_link "Create budget investment" + + expect(page).to have_content(accepting_budget.name) + expect(page).to have_content(reviewing_budget.name) + expect(page).to have_content(selecting_budget.name) + + expect(page).to_not have_content(valuating_budget.name) + expect(page).to_not have_content(balloting_budget.name) + expect(page).to_not have_content(reviewing_ballots_budget.name) + expect(page).to_not have_content(finished.name) + end + context "Supporting" do scenario 'Supporting budget investments on behalf of someone in index view', :js do diff --git a/spec/features/management/users_spec.rb b/spec/features/management/users_spec.rb index 694720cc0..5d08ce711 100644 --- a/spec/features/management/users_spec.rb +++ b/spec/features/management/users_spec.rb @@ -18,6 +18,7 @@ feature 'Users' do fill_in 'user_username', with: 'pepe' fill_in 'user_email', with: 'pepe@gmail.com' + select_date '31-December-1980', from: 'user_date_of_birth' click_button 'Create user' @@ -28,6 +29,7 @@ feature 'Users' do expect(user).to be_level_three_verified expect(user).to be_residence_verified expect(user).to_not be_confirmed + expect(user.date_of_birth).to have_content (Date.new(1980,12,31)) sent_token = /.*confirmation_token=(.*)".*/.match(ActionMailer::Base.deliveries.last.body.to_s)[1] visit user_confirmation_path(confirmation_token: sent_token) diff --git a/spec/features/moderation/comments_spec.rb b/spec/features/moderation/comments_spec.rb index dc3604c3a..65d2481ba 100644 --- a/spec/features/moderation/comments_spec.rb +++ b/spec/features/moderation/comments_spec.rb @@ -84,6 +84,8 @@ feature 'Moderate comments' do end scenario "select all/none", :js do + Capybara.current_driver = :poltergeist_no_js_errors + create_list(:comment, 2) visit moderation_comments_path diff --git a/spec/features/moderation/debates_spec.rb b/spec/features/moderation/debates_spec.rb index 78aca9b7e..8a729efe7 100644 --- a/spec/features/moderation/debates_spec.rb +++ b/spec/features/moderation/debates_spec.rb @@ -91,6 +91,8 @@ feature 'Moderate debates' do end scenario "select all/none", :js do + Capybara.current_driver = :poltergeist_no_js_errors + create_list(:debate, 2) visit moderation_debates_path diff --git a/spec/features/moderation/proposals_spec.rb b/spec/features/moderation/proposals_spec.rb index ab8da4764..6acff7c71 100644 --- a/spec/features/moderation/proposals_spec.rb +++ b/spec/features/moderation/proposals_spec.rb @@ -83,6 +83,8 @@ feature 'Moderate proposals' do end scenario "select all/none", :js do + Capybara.current_driver = :poltergeist_no_js_errors + create_list(:proposal, 2) visit moderation_proposals_path diff --git a/spec/features/moderation_spec.rb b/spec/features/moderation_spec.rb index 6607ec48a..fedcb105d 100644 --- a/spec/features/moderation_spec.rb +++ b/spec/features/moderation_spec.rb @@ -43,6 +43,20 @@ feature 'Moderation' do expect(page).to have_content "You do not have permission to access this page" end + scenario 'Access as poll officer is not authorized' do + create(:poll_officer, user: user) + + login_as(user) + visit root_path + + expect(page).to_not have_link("Moderation") + visit moderation_root_path + + expect(current_path).not_to eq(moderation_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + scenario 'Access as a moderator is authorized' do create(:moderator, user: user) diff --git a/spec/features/officing/final_recount_spec.rb b/spec/features/officing/final_recount_spec.rb new file mode 100644 index 000000000..1fcfa48a1 --- /dev/null +++ b/spec/features/officing/final_recount_spec.rb @@ -0,0 +1,147 @@ +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 and results' + 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 and results' + end + + within("#poll_#{@poll.id}") do + expect(page).to have_content(@poll.name) + click_link 'Add final recount' + end + + 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 final 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 + + scenario 'Show final and system recounts to compare' do + final_officer_assignment = create(:poll_officer_assignment, :final, officer: @poll_officer) + poll = final_officer_assignment.booth_assignment.poll + poll.update(ends_at: 1.day.ago) + final_recount = create(:poll_final_recount, + officer_assignment: final_officer_assignment, + booth_assignment: final_officer_assignment.booth_assignment, + date: 7.days.ago, + count: 100) + 33.times { create(:poll_voter, :valid_document, + poll: poll, + booth_assignment: final_officer_assignment.booth_assignment, + created_at: final_recount.date) } + + visit new_officing_poll_final_recount_path(poll) + within("#poll_final_recount_#{final_recount.id}") do + expect(page).to have_content(I18n.l(final_recount.date.to_date, format: :long)) + expect(page).to have_content(final_officer_assignment.booth_assignment.booth.name) + expect(page).to have_content('100') + expect(page).to have_content('33') + end + end + + scenario "Show link to add results for same booth/date" do + final_officer_assignment = create(:poll_officer_assignment, :final, officer: @poll_officer) + poll = final_officer_assignment.booth_assignment.poll + poll.update(ends_at: 1.day.ago) + final_recount = create(:poll_final_recount, + officer_assignment: final_officer_assignment, + booth_assignment: final_officer_assignment.booth_assignment, + date: 7.days.ago, + count: 100) + visit new_officing_poll_final_recount_path(poll) + within("#poll_final_recount_#{final_recount.id}") do + click_link "Add results" + end + + expected_path = new_officing_poll_result_path(poll, oa: final_recount.officer_assignment.id, d: I18n.l(final_recount.date.to_date)) + expect(page).to have_current_path(expected_path) + expect(page).to have_select('officer_assignment_id', selected: final_recount.booth_assignment.booth.name) + expect(page).to have_select('date', selected: I18n.l(final_recount.date.to_date, format: :long)) + end + +end \ No newline at end of file diff --git a/spec/features/officing/recount_spec.rb b/spec/features/officing/recount_spec.rb new file mode 100644 index 000000000..a7d74d0be --- /dev/null +++ b/spec/features/officing/recount_spec.rb @@ -0,0 +1,87 @@ +require 'rails_helper' + +feature 'Officing Recount' do + + background do + @poll_officer = create(:poll_officer) + @officer_assignment = create(:poll_officer_assignment, officer: @poll_officer) + @poll = @officer_assignment.booth_assignment.poll + login_as(@poll_officer.user) + end + + scenario 'Only polls where user is officer are accessible' do + not_allowed_poll = create(:poll) + + visit root_path + click_link 'Polling officers' + + expect(page).to have_content('Poll officing') + within('#side_menu') do + click_link 'Store recount' + end + + expect(page).to_not have_content(not_allowed_poll.name) + expect(page).to have_content(@poll.name) + + visit new_officing_poll_recount_path(not_allowed_poll) + expect(page).to have_content('You are not a poll officer for this poll') + end + + scenario 'Add recount' do + visit officing_root_path + + within('#side_menu') do + click_link 'Store recount' + end + + click_link @poll.name + + expect(page).to_not have_content('Your recounts') + + booth_name = @officer_assignment.booth_assignment.booth.name + date = I18n.l(@officer_assignment.date.to_date, format: :long) + select "#{booth_name}: #{date}", from: 'officer_assignment_id' + fill_in :count, with: '33' + click_button 'Save' + + expect(page).to have_content('Your recounts') + + within("#poll_recount_#{@officer_assignment.booth_assignment.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 + recount = create(:poll_recount, + officer_assignment: @officer_assignment, + booth_assignment: @officer_assignment.booth_assignment, + date: @officer_assignment.date, + count: 100) + + booth_name = @officer_assignment.booth_assignment.booth.name + date = I18n.l(@officer_assignment.date.to_date, format: :long) + + visit new_officing_poll_recount_path(@poll) + + expect(page).to have_content('Your recounts') + + within("#poll_recount_#{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}: #{date}", from: 'officer_assignment_id' + fill_in :count, with: '42' + click_button 'Save' + + within("#poll_recount_#{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/features/officing/residence_spec.rb b/spec/features/officing/residence_spec.rb new file mode 100644 index 000000000..69ffd9d21 --- /dev/null +++ b/spec/features/officing/residence_spec.rb @@ -0,0 +1,96 @@ +require 'rails_helper' + +feature 'Residence' do + let(:officer) { create(:poll_officer) } + + feature "Officers without assignments" do + + scenario "Can not access residence verification" do + login_as(officer.user) + visit officing_root_path + + within("#side_menu") do + click_link "Validate document" + end + + expect(page).to have_content("You don't have officing shifts today") + + create(:poll_officer_assignment, officer: officer, date: 1.day.from_now) + + visit new_officing_residence_path + + expect(page).to have_content("You don't have officing shifts today") + end + + end + + feature "Assigned officers" do + + background do + create(:poll_officer_assignment, officer: officer) + login_as(officer.user) + visit officing_root_path + end + + scenario "Verify voter" do + within("#side_menu") do + click_link "Validate document" + end + + select 'DNI', from: 'residence_document_type' + fill_in 'residence_document_number', with: "12345678Z" + fill_in 'residence_year_of_birth', with: '1980' + + click_button 'Validate document' + + expect(page).to have_content 'Document verified with Census' + end + + scenario "Error on verify" do + within("#side_menu") do + click_link "Validate document" + end + + click_button 'Validate document' + expect(page).to have_content(/\d errors? prevented the verification of this document/) + end + + scenario "Error on Census (document number)" do + initial_failed_census_calls_count = officer.failed_census_calls_count + within("#side_menu") do + click_link "Validate document" + end + + select 'DNI', from: 'residence_document_type' + fill_in 'residence_document_number', with: "9999999A" + fill_in 'residence_year_of_birth', with: '1980' + + click_button 'Validate document' + + expect(page).to have_content 'The Census was unable to verify this document' + + officer.reload + fcc = FailedCensusCall.last + expect(fcc).to be + expect(fcc.poll_officer).to eq(officer) + expect(officer.failed_census_calls.last).to eq(fcc) + expect(officer.failed_census_calls_count).to eq(initial_failed_census_calls_count + 1) + end + + scenario "Error on Census (year of birth)" do + within("#side_menu") do + click_link "Validate document" + end + + select 'DNI', from: 'residence_document_type' + fill_in 'residence_document_number', with: "12345678Z" + fill_in 'residence_year_of_birth', with: '1981' + + click_button 'Validate document' + + expect(page).to have_content 'The Census was unable to verify this document' + end + + end + +end diff --git a/spec/features/officing/results_spec.rb b/spec/features/officing/results_spec.rb new file mode 100644 index 000000000..1de3f4bdb --- /dev/null +++ b/spec/features/officing/results_spec.rb @@ -0,0 +1,159 @@ +require 'rails_helper' + +feature 'Officing Results' 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) + @question_1 = create(:poll_question, poll: @poll, valid_answers: "Yes,No") + @question_2 = create(:poll_question, poll: @poll, valid_answers: "Today,Tomorrow") + login_as(@poll_officer.user) + end + + scenario 'Only polls where user is officer for results 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 and results' + 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_result_path(not_allowed_poll_1) + expect(page).to have_content('You are allowed to add results for this poll') + end + + scenario 'Add results' do + visit officing_root_path + + within('#side_menu') do + click_link 'Final recounts and results' + end + + within("#poll_#{@poll.id}") do + expect(page).to have_content(@poll.name) + click_link 'Add results' + end + + expect(page).to_not have_content('Your results') + + 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 "questions[#{@question_1.id}][0]", with: '100' + fill_in "questions[#{@question_1.id}][1]", with: '200' + + fill_in "questions[#{@question_2.id}][0]", with: '333' + fill_in "questions[#{@question_2.id}][1]", with: '444' + + fill_in "whites", with: '66' + fill_in "nulls", with: '77' + + click_button 'Save' + + expect(page).to have_content('Your results') + + within("#results_#{@officer_assignment.booth_assignment_id}_#{@poll.starts_at.to_date.strftime('%Y%m%d')}") do + expect(page).to have_content(date) + expect(page).to have_content(booth_name) + end + end + + scenario 'Edit result' do + partial_result = create(:poll_partial_result, + officer_assignment: @officer_assignment, + booth_assignment: @officer_assignment.booth_assignment, + date: @poll.starts_at, + question: @question_1, + answer: @question_1.valid_answers[0], + author: @poll_officer.user, + amount: 7777) + + visit officing_poll_results_path(@poll, date: I18n.l(partial_result.date), booth_assignment_id: partial_result.booth_assignment_id) + + within("#question_#{@question_1.id}_0_result") { expect(page).to have_content('7777') } + + visit new_officing_poll_result_path(@poll) + + booth_name = partial_result.booth_assignment.booth.name + date = I18n.l(partial_result.date, format: :long) + select booth_name, from: 'officer_assignment_id' + select date, from: 'date' + + fill_in "questions[#{@question_1.id}][0]", with: '5555' + fill_in "questions[#{@question_1.id}][1]", with: '200' + fill_in "whites", with: '6' + fill_in "nulls", with: '7' + + click_button 'Save' + + within("#results_#{partial_result.booth_assignment_id}_#{partial_result.date.strftime('%Y%m%d')}") do + expect(page).to have_content(I18n.l(partial_result.date, format: :long)) + expect(page).to have_content(partial_result.booth_assignment.booth.name) + click_link "See results" + end + + expect(page).to_not have_content('7777') + within("#white_results") { expect(page).to have_content('6') } + within("#null_results") { expect(page).to have_content('7') } + within("#question_#{@question_1.id}_0_result") { expect(page).to have_content('5555') } + within("#question_#{@question_1.id}_1_result") { expect(page).to have_content('200') } + end + + scenario 'Index lists all questions and answers' do + partial_result = create(:poll_partial_result, + officer_assignment: @officer_assignment, + booth_assignment: @officer_assignment.booth_assignment, + date: @poll.ends_at, + question: @question_1, + amount: 33) + white_result = create(:poll_white_result, + officer_assignment: @officer_assignment, + booth_assignment: @officer_assignment.booth_assignment, + date: @poll.ends_at, + amount: 21) + null_result = create(:poll_null_result, + officer_assignment: @officer_assignment, + booth_assignment: @officer_assignment.booth_assignment, + date: @poll.ends_at, + amount: 44) + + visit officing_poll_results_path(@poll, + date: I18n.l(@poll.ends_at.to_date), + booth_assignment_id: @officer_assignment.booth_assignment_id) + + expect(page).to have_content(I18n.l(@poll.ends_at.to_date, format: :long)) + expect(page).to have_content(@officer_assignment.booth_assignment.booth.name) + + expect(page).to have_content(@question_1.title) + @question_1.valid_answers.each_with_index do |answer, i| + within("#question_#{@question_1.id}_#{i}_result") { expect(page).to have_content(answer) } + end + + expect(page).to have_content(@question_2.title) + @question_2.valid_answers.each_with_index do |answer, i| + within("#question_#{@question_2.id}_#{i}_result") { expect(page).to have_content(answer) } + end + + within('#white_results') { expect(page).to have_content('21') } + within('#null_results') { expect(page).to have_content('44') } + end + +end \ No newline at end of file diff --git a/spec/features/officing/voters_spec.rb b/spec/features/officing/voters_spec.rb new file mode 100644 index 000000000..2d28c7e77 --- /dev/null +++ b/spec/features/officing/voters_spec.rb @@ -0,0 +1,67 @@ +require 'rails_helper' + +feature 'Voters' do + + let(:officer) { create(:poll_officer) } + + background do + login_as(officer.user) + create(:geozone, :in_census) + + #remove once foundation.equalizer js error has been fixed + Capybara.current_driver = :poltergeist_no_js_errors + end + + scenario "Can vote", :js do + poll = create(:poll_officer_assignment, officer: officer).booth_assignment.poll + + visit new_officing_residence_path + officing_verify_residence + + expect(page).to have_content "Polls" + expect(page).to have_content poll.name + + click_button "Confirm vote" + + expect(page).to have_content "Vote introduced!" + expect(page).to_not have_button "Confirm vote" + + page.evaluate_script("window.location.reload()") + expect(page).to have_content "Has already participated in this poll" + expect(page).to_not have_button "Confirm vote" + end + + scenario "Already voted", :js do + poll1 = create(:poll) + poll2 = create(:poll) + + user = create(:user, :level_two) + voter = create(:poll_voter, poll: poll1, user: user) + + visit new_officing_voter_path(id: voter.user.id) + + within("#poll_#{poll1.id}") do + expect(page).to have_content "Has already participated in this poll" + expect(page).to_not have_button "Confirm vote" + end + + within("#poll_#{poll2.id}") do + expect(page).to have_button "Confirm vote" + end + end + + scenario "Had already verified his residence, but is not level 2 yet", :js do + user = create(:user, residence_verified_at: Time.current, document_type: "1", document_number: "12345678Z") + expect(user).to_not be_level_two_verified + poll = create(:poll_officer_assignment, officer: officer).booth_assignment.poll + + visit new_officing_residence_path + officing_verify_residence + + expect(page).to have_content "Polls" + expect(page).to have_content poll.name + end + + #Fix and use answerable_by(user) + xscenario "Display only answerable polls" +end diff --git a/spec/features/officing_spec.rb b/spec/features/officing_spec.rb new file mode 100644 index 000000000..d4cb416a1 --- /dev/null +++ b/spec/features/officing_spec.rb @@ -0,0 +1,109 @@ +require 'rails_helper' + +feature 'Poll Officing' do + let(:user) { create(:user) } + + scenario 'Access as regular user is not authorized' do + login_as(user) + visit root_path + + expect(page).to_not have_link("Polling officers") + visit officing_root_path + + expect(current_path).not_to eq(officing_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + + scenario 'Access as moderator is not authorized' do + create(:moderator, user: user) + login_as(user) + visit root_path + + expect(page).to_not have_link("Polling officers") + visit officing_root_path + + expect(current_path).not_to eq(officing_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + + scenario 'Access as manager is not authorized' do + create(:manager, user: user) + login_as(user) + visit root_path + + expect(page).to_not have_link("Polling officers") + visit officing_root_path + + expect(current_path).not_to eq(officing_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + + scenario 'Access as a valuator is not authorized' do + create(:valuator, user: user) + login_as(user) + visit root_path + + expect(page).to_not have_link("Polling officers") + visit officing_root_path + + expect(current_path).not_to eq(officing_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + + scenario 'Access as an poll officer is authorized' do + create(:poll_officer, user: user) + create(:poll) + login_as(user) + visit root_path + + expect(page).to have_link("Polling officers") + click_on "Polling officers" + + expect(current_path).to eq(officing_root_path) + expect(page).to_not have_content "You do not have permission to access this page" + end + + scenario 'Access as an administrator is authorized' do + create(:administrator, user: user) + create(:poll) + login_as(user) + visit root_path + + expect(page).to have_link("Polling officers") + click_on "Polling officers" + + expect(current_path).to eq(officing_root_path) + expect(page).to_not have_content "You do not have permission to access this page" + end + + scenario "Poll officer access links" do + create(:poll_officer, user: user) + login_as(user) + visit root_path + + expect(page).to have_link("Polling officers") + expect(page).to_not have_link('Valuation') + expect(page).to_not have_link('Administration') + expect(page).to_not have_link('Moderation') + end + + scenario 'Officing dashboard' do + create(:poll_officer, user: user) + create(:poll) + login_as(user) + visit root_path + + click_link 'Polling officers' + + expect(current_path).to eq(officing_root_path) + expect(page).to have_css('#officing_menu') + expect(page).to_not have_css('#valuation_menu') + expect(page).to_not have_css('#admin_menu') + expect(page).to_not have_css('#moderation_menu') + end + +end \ No newline at end of file diff --git a/spec/features/polls/polls_spec.rb b/spec/features/polls/polls_spec.rb new file mode 100644 index 000000000..047c71146 --- /dev/null +++ b/spec/features/polls/polls_spec.rb @@ -0,0 +1,197 @@ +require 'rails_helper' + +feature 'Polls' do + + context '#index' do + + scenario 'Polls can be listed' do + polls = create_list(:poll, 3) + + visit polls_path + + polls.each do |poll| + expect(page).to have_content(poll.name) + expect(page).to have_link("Participate in this poll") + end + end + + scenario 'Filtering polls' do + create(:poll, name: "Current poll") + create(:poll, :incoming, name: "Incoming poll") + create(:poll, :expired, name: "Expired poll") + + visit polls_path + expect(page).to have_content('Current poll') + expect(page).to have_link('Participate in this poll') + expect(page).to_not have_content('Incoming poll') + expect(page).to_not have_content('Expired poll') + + visit polls_path(filter: 'incoming') + expect(page).to_not have_content('Current poll') + expect(page).to have_content('Incoming poll') + expect(page).to have_link('More information') + expect(page).to_not have_content('Expired poll') + + visit polls_path(filter: 'expired') + expect(page).to_not have_content('Current poll') + expect(page).to_not have_content('Incoming poll') + expect(page).to have_content('Expired poll') + expect(page).to have_link('Poll ended') + end + + scenario "Current filter is properly highlighted" do + visit polls_path + expect(page).to_not have_link('Open') + expect(page).to have_link('Incoming') + expect(page).to have_link('Expired') + + visit polls_path(filter: 'incoming') + expect(page).to have_link('Open') + expect(page).to_not have_link('Incoming') + expect(page).to have_link('Expired') + + visit polls_path(filter: 'expired') + expect(page).to have_link('Open') + expect(page).to have_link('Incoming') + expect(page).to_not have_link('Expired') + end + end + + context 'Show' do + let(:geozone) { create(:geozone) } + let(:poll) { create(:poll) } + + scenario 'Lists questions from proposals as well as regular ones' do + normal_question = create(:poll_question, poll: poll) + proposal_question = create(:poll_question, poll: poll, proposal: create(:proposal)) + + visit poll_path(poll) + expect(page).to have_content(poll.name) + + expect(page).to have_content(normal_question.title) + expect(page).to have_content(proposal_question.title) + end + + scenario 'Non-logged in users' do + create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + visit poll_path(poll) + + expect(page).to have_content('Han Solo') + expect(page).to have_content('Chewbacca') + expect(page).to have_content('You must Sign in or Sign up to participate') + + expect(page).to_not have_link('Han Solo') + expect(page).to_not have_link('Chewbacca') + end + + scenario 'Level 1 users' do + poll.update(geozone_restricted: true) + poll.geozones << geozone + create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + login_as(create(:user, geozone: geozone)) + visit poll_path(poll) + + expect(page).to have_content('You must verify your account in order to answer') + + expect(page).to have_content('Han Solo') + expect(page).to have_content('Chewbacca') + + expect(page).to_not have_link('Han Solo') + expect(page).to_not have_link('Chewbacca') + end + + scenario 'Level 2 users in an incoming poll' do + incoming_poll = create(:poll, :incoming, geozone_restricted: true) + incoming_poll.geozones << geozone + create(:poll_question, poll: incoming_poll, valid_answers: 'Rey, Finn') + login_as(create(:user, :level_two, geozone: geozone)) + + visit poll_path(incoming_poll) + + expect(page).to have_content('Rey') + expect(page).to have_content('Finn') + expect(page).to_not have_link('Rey') + expect(page).to_not have_link('Finn') + + expect(page).to have_content('This poll has not yet started') + end + + scenario 'Level 2 users in an expired poll' do + expired_poll = create(:poll, :expired, geozone_restricted: true) + expired_poll.geozones << geozone + create(:poll_question, poll: expired_poll, valid_answers: 'Luke, Leia') + login_as(create(:user, :level_two, geozone: geozone)) + + visit poll_path(expired_poll) + + expect(page).to have_content('Luke') + expect(page).to have_content('Leia') + expect(page).to_not have_link('Luke') + expect(page).to_not have_link('Leia') + + expect(page).to have_content('This poll has finished') + end + + scenario 'Level 2 users in a poll with questions for a geozone which is not theirs' do + poll.update(geozone_restricted: true) + poll.geozones << create(:geozone) + create(:poll_question, poll: poll, valid_answers: 'Vader, Palpatine') + login_as(create(:user, :level_two)) + + visit poll_path(poll) + + expect(page).to have_content('Vader') + expect(page).to have_content('Palpatine') + expect(page).to_not have_link('Vader') + expect(page).to_not have_link('Palpatine') + end + + scenario 'Level 2 users reading a same-geozone poll' do + poll.update(geozone_restricted: true) + poll.geozones << geozone + create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + login_as(create(:user, :level_two, geozone: geozone)) + visit poll_path(poll) + + expect(page).to have_link('Han Solo') + expect(page).to have_link('Chewbacca') + end + + scenario 'Level 2 users reading a all-geozones poll' do + create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + login_as(create(:user, :level_two)) + visit poll_path(poll) + + expect(page).to have_link('Han Solo') + expect(page).to have_link('Chewbacca') + end + + scenario 'Level 2 users who have already answered' do + question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + user = create(:user, :level_two) + create(:poll_answer, question: question, author: user, answer: 'Chewbacca') + + login_as user + visit poll_path(poll) + + expect(page).to have_link('Han Solo') + expect(page).to_not have_link('Chewbacca') + expect(page).to have_content('Chewbacca') + end + + scenario 'Level 2 users answering', :js do + poll.update(geozone_restricted: true) + poll.geozones << geozone + create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + user = create(:user, :level_two, geozone: geozone) + login_as user + visit poll_path(poll) + + click_link 'Han Solo' + + expect(page).to_not have_link('Han Solo') + expect(page).to have_link('Chewbacca') + end + + end +end diff --git a/spec/features/polls/questions_spec.rb b/spec/features/polls/questions_spec.rb new file mode 100644 index 000000000..6f2225175 --- /dev/null +++ b/spec/features/polls/questions_spec.rb @@ -0,0 +1,112 @@ +require 'rails_helper' + +feature 'Poll Questions' do + + scenario 'Lists questions from proposals before regular questions' do + poll = create(:poll) + normal_question = create(:poll_question, poll: poll) + proposal_question = create(:poll_question, proposal: create(:proposal), poll: poll) + + visit poll_path(poll) + + expect(proposal_question.title).to appear_before(normal_question.title) + end + + scenario 'shows the author visible name instead of a link to the author' do + poll = create(:poll) + question_with_author = create(:poll_question, poll: poll) + question_with_author_visible_name = create(:poll_question, poll: poll, author_visible_name: 'potato') + + visit question_path(question_with_author) + expect(page).to have_link(question_with_author.author.name) + + visit question_path(question_with_author_visible_name) + expect(page).to_not have_link(question_with_author_visible_name.author.name) + expect(page).to have_content(question_with_author_visible_name.author_visible_name) + end + + context 'Answering' do + let(:geozone) { create(:geozone) } + let(:poll) { create(:poll, geozone_restricted: true, geozone_ids: [geozone.id]) } + + scenario 'Non-logged in users' do + question = create(:poll_question, valid_answers: 'Han Solo, Chewbacca') + + visit question_path(question) + + expect(page).to have_content('You must Sign in or Sign up to participate') + end + + scenario 'Level 1 users' do + question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + + login_as(create(:user, geozone: geozone)) + visit question_path(question) + + expect(page).to have_content('You must verify your account in order to answer') + end + + scenario 'Level 2 users in an poll question for a geozone which is not theirs' do + + other_poll = create(:poll, geozone_restricted: true, geozone_ids: [create(:geozone).id]) + question = create(:poll_question, poll: other_poll, valid_answers: 'Vader, Palpatine') + + login_as(create(:user, :level_two, geozone: geozone)) + visit question_path(question) + + expect(page).to have_content('This question is not available on your geozone') + end + + scenario 'Level 2 users who can answer' do + question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + + login_as(create(:user, :level_two, geozone: geozone)) + visit question_path(question) + + expect(page).to have_link('Answer this question') + end + + scenario 'Level 2 users who have already answered' do + question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + + user = create(:user, :level_two, geozone: geozone) + create(:poll_answer, question: question, author: user, answer: 'Chewbacca') + + login_as user + visit question_path(question) + + expect(page).to have_link('Answer this question') + end + + scenario 'Level 2 users answering', :js do + question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + user = create(:user, :level_two, geozone: geozone) + + login_as user + visit question_path(question) + + expect(page).to have_link('Answer this question') + end + + scenario 'Records participation', :js do + question = create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca') + user = create(:user, :level_two, geozone: geozone, gender: 'female', date_of_birth: 33.years.ago) + + login_as user + visit question_path(question) + + click_link 'Answer this question' + click_link 'Han Solo' + + expect(page).to_not have_link('Han Solo') + + voter = poll.voters.first + expect(voter.document_number).to eq(user.document_number) + expect(voter.geozone_id).to eq(user.geozone_id) + expect(voter.gender).to eq(user.gender) + expect(voter.age).to eq(33) + expect(voter.poll_id).to eq(poll.id) + end + + end +end diff --git a/spec/features/proposal_ballots_spec.rb b/spec/features/proposal_ballots_spec.rb index 01ba71bf7..8f5e99eb8 100644 --- a/spec/features/proposal_ballots_spec.rb +++ b/spec/features/proposal_ballots_spec.rb @@ -10,7 +10,7 @@ feature 'Proposal ballots' do expect(page).to_not have_css("#next-voting") expect(page).to have_css("#featured-proposals") - create_successfull_proposals + create_successful_proposals visit proposals_path @@ -18,22 +18,22 @@ feature 'Proposal ballots' do expect(page).to_not have_css("#featured-proposals") end - scenario 'Successfull proposals do not show support buttons in index' do - successfull_proposals = create_successfull_proposals + scenario 'Successful proposals do not show support buttons in index' do + successful_proposals = create_successful_proposals visit proposals_path - successfull_proposals.each do |proposal| + successful_proposals.each do |proposal| within("#proposal_#{proposal.id}_votes") do expect(page).to have_content "This proposal has reached the required supports" end end end - scenario 'Successfull proposals do not show support buttons in show' do - successfull_proposals = create_successfull_proposals + scenario 'Successful proposals do not show support buttons in show' do + successful_proposals = create_successful_proposals - successfull_proposals.each do |proposal| + successful_proposals.each do |proposal| visit proposal_path(proposal) within("#proposal_#{proposal.id}_votes") do expect(page).to have_content "This proposal has reached the required supports" @@ -41,15 +41,5 @@ feature 'Proposal ballots' do end end - scenario 'Successfull proposals are listed in the proposal ballots index' do - successfull_proposals = create_successfull_proposals - - visit proposal_ballots_path - - successfull_proposals.each do |proposal| - expect(page).to have_content(proposal.title) - end - end - end diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 18864dc9b..843b74bf5 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -63,7 +63,7 @@ feature 'Proposals' do expect(page.html).to include "#{proposal.title}" within('.social-share-button') do - expect(page.all('a').count).to be(3) # Twitter, Facebook, Google+ + expect(page.all('a').count).to be(4) # Twitter, Facebook, Google+, Telegram end end @@ -980,6 +980,28 @@ feature 'Proposals' do end end + scenario "Search by custom invalid date range", :js do + proposal1 = create(:proposal, created_at: 2.days.ago) + proposal2 = create(:proposal, created_at: 3.days.ago) + proposal3 = create(:proposal, created_at: 9.days.ago) + + visit proposals_path + + click_link "Advanced search" + select "Customized", from: "js-advanced-search-date-min" + fill_in "advanced_search_date_min", with: 4000.years.ago + fill_in "advanced_search_date_max", with: "wrong date" + click_button "Filter" + + expect(page).to have_content("There are 3 citizen proposals") + + within("#proposals") do + expect(page).to have_content(proposal1.title) + expect(page).to have_content(proposal2.title) + expect(page).to have_content(proposal3.title) + end + end + scenario "Search by multiple filters", :js do ana = create :user, official_level: 1 john = create :user, official_level: 1 @@ -1350,3 +1372,69 @@ feature 'Proposals' do end end + +feature 'Successful proposals' do + + scenario 'Banner shows in proposal index' do + create_featured_proposals + + visit proposals_path + expect(page).to_not have_css("#next-voting") + expect(page).to have_css("#featured-proposals") + + create_successful_proposals + + visit proposals_path + + expect(page).to have_css("#next-voting") + expect(page).to_not have_css("#featured-proposals") + end + + scenario 'Successful proposals do not show support buttons in index' do + successful_proposals = create_successful_proposals + + visit proposals_path + + successful_proposals.each do |proposal| + within("#proposal_#{proposal.id}_votes") do + expect(page).to_not have_css(".supports") + expect(page).to have_content "This proposal has reached the required supports" + end + end + end + + scenario 'Successful proposals do not show support buttons in show' do + successful_proposals = create_successful_proposals + + successful_proposals.each do |proposal| + visit proposal_path(proposal) + within("#proposal_#{proposal.id}_votes") do + expect(page).to_not have_css(".supports") + expect(page).to have_content "This proposal has reached the required supports" + end + end + end + + scenario 'Successful proposals show create question button to admin users' do + successful_proposals = create_successful_proposals + + visit proposals_path + + successful_proposals.each do |proposal| + within("#proposal_#{proposal.id}_votes") do + expect(page).to_not have_link "Create question" + end + end + + login_as(create(:administrator).user) + + visit proposals_path + + successful_proposals.each do |proposal| + within("#proposal_#{proposal.id}_votes") do + expect(page).to have_link "Create question" + end + end + + end +end \ No newline at end of file diff --git a/spec/features/site_customization/content_blocks_spec.rb b/spec/features/site_customization/content_blocks_spec.rb new file mode 100644 index 000000000..995982063 --- /dev/null +++ b/spec/features/site_customization/content_blocks_spec.rb @@ -0,0 +1,33 @@ +require 'rails_helper' + +feature "Custom content blocks" do + scenario "top links" do + create(:site_customization_content_block, name: "top_links", locale: "en", body: "content for top links") + create(:site_customization_content_block, name: "top_links", locale: "es", body: "contenido para top links") + + visit "/?locale=en" + + expect(page).to have_content("content for top links") + expect(page).to_not have_content("contenido para top links") + + visit "/?locale=es" + + expect(page).to have_content("contenido para top links") + expect(page).to_not have_content("content for top links") + end + + scenario "footer" do + create(:site_customization_content_block, name: "footer", locale: "en", body: "content for footer") + create(:site_customization_content_block, name: "footer", locale: "es", body: "contenido para footer") + + visit "/?locale=en" + + expect(page).to have_content("content for footer") + expect(page).to_not have_content("contenido para footer") + + visit "/?locale=es" + + expect(page).to have_content("contenido para footer") + expect(page).to_not have_content("content for footer") + end +end diff --git a/spec/features/site_customization/custom_pages_spec.rb b/spec/features/site_customization/custom_pages_spec.rb new file mode 100644 index 000000000..2e2fac84a --- /dev/null +++ b/spec/features/site_customization/custom_pages_spec.rb @@ -0,0 +1,102 @@ +require 'rails_helper' + +feature "Custom Pages" do + context "Override existing page" do + scenario "See default content when custom page is not published" do + custom_page = create(:site_customization_page, + slug: "conditions", + title: "Custom conditions", + content: "New text for conditions page", + print_content_flag: true + ) + + visit custom_page.url + + expect(page).to have_title("Terms of use") + expect(page).to have_selector("h1", text: "Terms and conditions of use") + expect(page).to have_content("Página de información sobre las condiciones de uso, privacidad y protección de datos personales.") + expect(page).to have_content("Print this info") + end + + scenario "See custom content when custom page is published" do + custom_page = create(:site_customization_page, :published, + slug: "conditions", + title: "Custom conditions", + content: "New text for conditions page", + print_content_flag: true + ) + + visit custom_page.url + + expect(page).to have_title("Custom conditions") + expect(page).to have_selector("h1", text: "Custom conditions") + expect(page).to have_content("New text for conditions page") + expect(page).to have_content("Print this info") + end + end + + context "New custom page" do + context "Draft" do + scenario "See page" do + custom_page = create(:site_customization_page, + slug: "other-slug", + title: "Custom page", + content: "Text for new custom page", + print_content_flag: false + ) + + visit custom_page.url + + expect(page.status_code).to eq(404) + end + end + + context "Published" do + scenario "See page" do + custom_page = create(:site_customization_page, :published, + slug: "other-slug", + title: "Custom page", + content: "Text for new custom page", + print_content_flag: false + ) + + visit custom_page.url + + expect(page).to have_title("Custom page") + expect(page).to have_selector("h1", text: "Custom page") + expect(page).to have_content("Text for new custom page") + expect(page).to_not have_content("Print this info") + end + + scenario "Listed in more information page" do + custom_page = create(:site_customization_page, :published, + slug: "another-slug", title: "Another custom page", + subtitle: "Subtitle for custom page", + more_info_flag: true + ) + + visit more_info_path + + expect(page).to have_content("Another custom page") + end + + scenario "Not listed in more information page" do + custom_page = create(:site_customization_page, :published, + slug: "another-slug", title: "Another custom page", + subtitle: "Subtitle for custom page", + more_info_flag: false + ) + + visit more_info_path + + expect(page).to_not have_content("Another custom page") + + visit custom_page.url + + expect(page).to have_title("Another custom page") + expect(page).to have_selector("h1", text: "Another custom page") + expect(page).to have_content("Subtitle for custom page") + end + end + end +end diff --git a/spec/features/valuation/budget_investments_spec.rb b/spec/features/valuation/budget_investments_spec.rb index bac7d1fe2..81e4eceae 100644 --- a/spec/features/valuation/budget_investments_spec.rb +++ b/spec/features/valuation/budget_investments_spec.rb @@ -13,6 +13,12 @@ feature 'Valuation budget investments' do expect{ visit valuation_budget_budget_investments_path(create(:budget)) }.to raise_exception(FeatureFlags::FeatureDisabled) end + scenario 'Display link to valuation section' do + Setting['feature.budgets'] = true + visit root_path + expect(page).to have_link "Valuation", href: valuation_root_path + end + scenario 'Index shows budget investments assigned to current valuator' do investment1 = create(:budget_investment, budget: @budget) investment2 = create(:budget_investment, budget: @budget) @@ -55,6 +61,8 @@ feature 'Valuation budget investments' do end scenario "Index filtering by heading", :js do + Capybara.current_driver = :poltergeist_no_js_errors + group = create(:budget_group, budget: @budget) heading1 = create(:budget_heading, name: "District 9", group: group) heading2 = create(:budget_heading, name: "Down to the river", group: group) diff --git a/spec/features/valuation/spending_proposals_spec.rb b/spec/features/valuation/spending_proposals_spec.rb index 25c5dfa61..035ed59fd 100644 --- a/spec/features/valuation/spending_proposals_spec.rb +++ b/spec/features/valuation/spending_proposals_spec.rb @@ -83,6 +83,8 @@ feature 'Valuation spending proposals' do end scenario "Index filtering by geozone", :js do + Capybara.current_driver = :poltergeist_no_js_errors + geozone = create(:geozone, name: "District 9") spending_proposal1 = create(:spending_proposal, title: "Realocate visitors", geozone: geozone) spending_proposal2 = create(:spending_proposal, title: "Destroy the city") diff --git a/spec/features/valuation_spec.rb b/spec/features/valuation_spec.rb index 2dcdc91c1..a0a4c9ba5 100644 --- a/spec/features/valuation_spec.rb +++ b/spec/features/valuation_spec.rb @@ -46,6 +46,19 @@ feature 'Valuation' do expect(page).to have_content "You do not have permission to access this page" end + scenario 'Access as poll officer is not authorized' do + create(:poll_officer, user: user) + login_as(user) + visit root_path + + expect(page).to_not have_link("Valuation") + visit valuation_root_path + + expect(current_path).not_to eq(valuation_root_path) + expect(current_path).to eq(proposals_path) + expect(page).to have_content "You do not have permission to access this page" + end + scenario 'Access as a valuator is authorized' do create(:valuator, user: user) login_as(user) diff --git a/spec/features/verification/residence_spec.rb b/spec/features/verification/residence_spec.rb index cdf33dfab..ed2ffd083 100644 --- a/spec/features/verification/residence_spec.rb +++ b/spec/features/verification/residence_spec.rb @@ -22,6 +22,31 @@ feature 'Residence' do expect(page).to have_content 'Residence verified' end + scenario 'When trying to verify a deregistered account old votes are reassigned' do + erased_user = create(:user, document_number: '12345678Z', document_type: '1', erased_at: Time.current) + vote = create(:vote, voter: erased_user) + new_user = create(:user) + + login_as(new_user) + + visit account_path + click_link 'Verify my account' + + fill_in 'residence_document_number', with: '12345678Z' + select 'DNI', from: 'residence_document_type' + select_date '31-December-1980', from: 'residence_date_of_birth' + fill_in 'residence_postal_code', with: '28013' + check 'residence_terms_of_service' + + click_button 'Verify residence' + + expect(page).to have_content 'Residence verified' + + expect(vote.reload.voter).to eq(new_user) + expect(erased_user.reload.document_number).to be_blank + expect(new_user.reload.document_number).to eq('12345678Z') + end + scenario 'Error on verify' do user = create(:user) login_as(user) @@ -102,24 +127,4 @@ feature 'Residence' do expect(page).to have_content "You have reached the maximum number of attempts. Please try again later." expect(current_path).to eq(account_path) end - - scenario 'Error when trying to verify a deregistered account' do - create(:user, document_number: '12345678Z', document_type: '1', erased_at: Time.now) - - login_as(create(:user)) - - visit account_path - click_link 'Verify my account' - - fill_in 'residence_document_number', with: "12345678Z" - select 'DNI', from: 'residence_document_type' - select_date '31-December-1980', from: 'residence_date_of_birth' - fill_in 'residence_postal_code', with: '28013' - check 'residence_terms_of_service' - - click_button 'Verify residence' - - expect(page).to_not have_content 'Residence verified' - expect(page).to have_content 'has already been taken' - end end diff --git a/spec/fixtures/files/logo_header.png b/spec/fixtures/files/logo_header.png new file mode 100644 index 000000000..5f5355574 Binary files /dev/null and b/spec/fixtures/files/logo_header.png differ diff --git a/spec/fixtures/files/social-media-icon.png b/spec/fixtures/files/social-media-icon.png new file mode 100644 index 000000000..fcd00e212 Binary files /dev/null and b/spec/fixtures/files/social-media-icon.png differ diff --git a/spec/helpers/admin_helper_spec.rb b/spec/helpers/admin_helper_spec.rb new file mode 100644 index 000000000..0239f1926 --- /dev/null +++ b/spec/helpers/admin_helper_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +describe AdminHelper do + + describe "#admin_submit_action" do + + it "returns new when the the resource has not been persisted" do + poll = build(:poll) + expect(admin_submit_action(poll)).to eq("new") + end + + it "returns edit when the the resource has been persisted" do + poll = create(:poll) + expect(admin_submit_action(poll)).to eq("edit") + end + + end + +end \ No newline at end of file diff --git a/spec/lib/age_spec.rb b/spec/lib/age_spec.rb new file mode 100644 index 000000000..6e1012fd4 --- /dev/null +++ b/spec/lib/age_spec.rb @@ -0,0 +1,58 @@ +require 'rails_helper' + +describe Age do + describe '.in_years' do + it "handles nils" do + expect(Age.in_years(nil)).to be_nil + end + + it "calculates age correctly for common dates" do + d = Date.new(1980, 3, 13) + expect(Age.in_years(d, Date.new(2000, 3, 12))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 3, 13))).to eq(20) + expect(Age.in_years(d, Date.new(2000, 3, 14))).to eq(20) + end + + it "calculates age correctly for people born near a year's limit" do + d = Date.new(1980, 12, 31) + expect(Age.in_years(d, Date.new(2000, 12, 30))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 12, 31))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 1, 1))).to eq(20) + + d = Date.new(1980, 1, 1) + expect(Age.in_years(d, Date.new(2000, 12, 31))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 1, 1))).to eq(21) + expect(Age.in_years(d, Date.new(2001, 1, 2))).to eq(21) + end + + it "calculates age correctly for people born around February the 29th" do + # 1980 and 2000 are leap years. 2001 is a regular year + d = Date.new(1980, 2, 29) + expect(Age.in_years(d, Date.new(2000, 2, 27))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 2, 28))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 2, 29))).to eq(20) + expect(Age.in_years(d, Date.new(2000, 3, 1))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 2, 27))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 2, 28))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 3, 1))).to eq(21) + + d = Date.new(1980, 2, 28) + expect(Age.in_years(d, Date.new(2000, 2, 27))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 2, 28))).to eq(20) + expect(Age.in_years(d, Date.new(2000, 2, 29))).to eq(20) + expect(Age.in_years(d, Date.new(2000, 3, 1))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 2, 27))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 2, 28))).to eq(21) + expect(Age.in_years(d, Date.new(2001, 3, 1))).to eq(21) + + d = Date.new(1980, 3, 1) + expect(Age.in_years(d, Date.new(2000, 2, 27))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 2, 28))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 2, 29))).to eq(19) + expect(Age.in_years(d, Date.new(2000, 3, 1))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 2, 27))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 2, 28))).to eq(20) + expect(Age.in_years(d, Date.new(2001, 3, 1))).to eq(21) + end + end +end diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index 8c4e8a1ef..5312221b8 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -67,5 +67,4 @@ describe "Abilities::Administrator" do it { should be_able_to(:valuate, create(:budget_investment, budget: create(:budget, phase: 'valuating'))) } it { should be_able_to(:valuate, create(:budget_investment, budget: create(:budget, phase: 'finished'))) } - end diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index f82bcc839..7c02f50fd 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -3,12 +3,17 @@ require 'cancan/matchers' describe "Abilities::Common" do subject(:ability) { Ability.new(user) } + let(:geozone) { create(:geozone) } - let(:user) { create(:user) } + let(:user) { create(:user, geozone: geozone) } + + let(:debate) { create(:debate) } + let(:comment) { create(:comment) } + let(:proposal) { create(:proposal) } + let(:own_debate) { create(:debate, author: user) } + let(:own_comment) { create(:comment, author: user) } + let(:own_proposal) { create(:proposal, author: user) } - let(:debate) { create(:debate) } - let(:comment) { create(:comment) } - let(:proposal) { create(:proposal) } let(:accepting_budget) { create(:budget, phase: 'accepting') } let(:reviewing_budget) { create(:budget, phase: 'reviewing') } let(:selecting_budget) { create(:budget, phase: 'selecting') } @@ -25,122 +30,188 @@ describe "Abilities::Common" do let(:ballot_in_accepting_budget) { create(:budget_ballot, budget: accepting_budget) } let(:ballot_in_selecting_budget) { create(:budget_ballot, budget: selecting_budget) } let(:ballot_in_balloting_budget) { create(:budget_ballot, budget: balloting_budget) } - let(:own_debate) { create(:debate, author: user) } - let(:own_comment) { create(:comment, author: user) } - let(:own_proposal) { create(:proposal, author: user) } + + let(:current_poll) { create(:poll) } + let(:incoming_poll) { create(:poll, :incoming) } + let(:incoming_poll_from_own_geozone) { create(:poll, :incoming, geozone_restricted: true, geozones: [geozone]) } + let(:incoming_poll_from_other_geozone) { create(:poll, :incoming, geozone_restricted: true, geozones: [create(:geozone)]) } + let(:expired_poll) { create(:poll, :expired) } + let(:expired_poll_from_own_geozone) { create(:poll, :expired, geozone_restricted: true, geozones: [geozone]) } + let(:expired_poll_from_other_geozone) { create(:poll, :expired, geozone_restricted: true, geozones: [create(:geozone)]) } + let(:poll) { create(:poll, geozone_restricted: false) } + let(:poll_from_own_geozone) { create(:poll, geozone_restricted: true, geozones: [geozone]) } + let(:poll_from_other_geozone) { create(:poll, geozone_restricted: true, geozones: [create(:geozone)]) } + + let(:poll_question_from_own_geozone) { create(:poll_question, poll: poll_from_own_geozone) } + let(:poll_question_from_other_geozone) { create(:poll_question, poll: poll_from_other_geozone) } + let(:poll_question_from_all_geozones) { create(:poll_question, poll: poll) } + + let(:expired_poll_question_from_own_geozone) { create(:poll_question, poll: expired_poll_from_own_geozone) } + let(:expired_poll_question_from_other_geozone) { create(:poll_question, poll: expired_poll_from_other_geozone) } + let(:expired_poll_question_from_all_geozones) { create(:poll_question, poll: expired_poll) } + + let(:incoming_poll_question_from_own_geozone) { create(:poll_question, poll: incoming_poll_from_own_geozone) } + let(:incoming_poll_question_from_other_geozone) { create(:poll_question, poll: incoming_poll_from_other_geozone) } + let(:incoming_poll_question_from_all_geozones) { create(:poll_question, poll: incoming_poll) } it { should be_able_to(:index, Debate) } - it { should be_able_to(:show, debate) } - it { should be_able_to(:vote, debate) } + it { should be_able_to(:show, debate) } + it { should be_able_to(:vote, debate) } it { should be_able_to(:show, user) } it { should be_able_to(:edit, user) } it { should be_able_to(:create, Comment) } - it { should be_able_to(:vote, Comment) } + it { should be_able_to(:vote, Comment) } - it { should be_able_to(:index, Proposal) } - it { should be_able_to(:show, proposal) } + it { should be_able_to(:index, Proposal) } + it { should be_able_to(:show, proposal) } it { should_not be_able_to(:vote, Proposal) } it { should_not be_able_to(:vote_featured, Proposal) } - it { should be_able_to(:index, SpendingProposal) } - it { should_not be_able_to(:create, SpendingProposal) } + it { should be_able_to(:index, SpendingProposal) } + it { should_not be_able_to(:create, SpendingProposal) } it { should_not be_able_to(:destroy, SpendingProposal) } - it { should_not be_able_to(:comment_as_administrator, debate) } - it { should_not be_able_to(:comment_as_moderator, debate) } + it { should_not be_able_to(:comment_as_administrator, debate) } + it { should_not be_able_to(:comment_as_moderator, debate) } it { should_not be_able_to(:comment_as_administrator, proposal) } - it { should_not be_able_to(:comment_as_moderator, proposal) } + it { should_not be_able_to(:comment_as_moderator, proposal) } - it { should be_able_to(:new, DirectMessage) } + it { should be_able_to(:new, DirectMessage) } it { should_not be_able_to(:create, DirectMessage) } - it { should_not be_able_to(:show, DirectMessage) } + it { should_not be_able_to(:show, DirectMessage) } describe 'flagging content' do - it { should be_able_to(:flag, debate) } + it { should be_able_to(:flag, debate) } it { should be_able_to(:unflag, debate) } - it { should be_able_to(:flag, comment) } + it { should be_able_to(:flag, comment) } it { should be_able_to(:unflag, comment) } - it { should be_able_to(:flag, proposal) } + it { should be_able_to(:flag, proposal) } it { should be_able_to(:unflag, proposal) } describe "own content" do - it { should_not be_able_to(:flag, own_comment) } + it { should_not be_able_to(:flag, own_comment) } it { should_not be_able_to(:unflag, own_comment) } - it { should_not be_able_to(:flag, own_debate) } + it { should_not be_able_to(:flag, own_debate) } it { should_not be_able_to(:unflag, own_debate) } - it { should_not be_able_to(:flag, own_proposal) } + it { should_not be_able_to(:flag, own_proposal) } it { should_not be_able_to(:unflag, own_proposal) } end end describe "other users" do let(:other_user) { create(:user) } - it { should be_able_to(:show, other_user) } + + it { should be_able_to(:show, other_user) } it { should_not be_able_to(:edit, other_user) } end describe "editing debates" do let(:own_debate_non_editable) { create(:debate, author: user) } + before { allow(own_debate_non_editable).to receive(:editable?).and_return(false) } - it { should be_able_to(:edit, own_debate) } - it { should_not be_able_to(:edit, debate) } # Not his + it { should be_able_to(:edit, own_debate) } + it { should_not be_able_to(:edit, debate) } # Not his it { should_not be_able_to(:edit, own_debate_non_editable) } end describe "editing proposals" do let(:own_proposal_non_editable) { create(:proposal, author: user) } + before { allow(own_proposal_non_editable).to receive(:editable?).and_return(false) } - it { should be_able_to(:edit, own_proposal) } - it { should_not be_able_to(:edit, proposal) } # Not his + it { should be_able_to(:edit, own_proposal) } + it { should_not be_able_to(:edit, proposal) } # Not his it { should_not be_able_to(:edit, own_proposal_non_editable) } end describe "when level 2 verified" do let(:own_spending_proposal) { create(:spending_proposal, author: user) } + let(:own_direct_message) { create(:direct_message, sender: user) } before{ user.update(residence_verified_at: Time.current, confirmed_phone: "1") } - it { should be_able_to(:vote, Proposal) } - it { should be_able_to(:vote_featured, Proposal) } + describe "Proposal" do + it { should be_able_to(:vote, Proposal) } + it { should be_able_to(:vote_featured, Proposal) } + end - it { should be_able_to(:create, SpendingProposal) } - it { should_not be_able_to(:destroy, create(:spending_proposal)) } - it { should_not be_able_to(:destroy, own_spending_proposal) } + describe "Spending Proposal" do + it { should be_able_to(:create, SpendingProposal) } + it { should_not be_able_to(:destroy, create(:spending_proposal)) } + it { should_not be_able_to(:destroy, own_spending_proposal) } + end - it { should be_able_to(:create, investment_in_accepting_budget) } - it { should_not be_able_to(:create, investment_in_selecting_budget) } - it { should_not be_able_to(:create, investment_in_balloting_budget) } + describe "Direct Message" do + it { should be_able_to(:new, DirectMessage) } + it { should be_able_to(:create, DirectMessage) } + it { should be_able_to(:show, own_direct_message) } + it { should_not be_able_to(:show, create(:direct_message)) } + end - it { should_not be_able_to(:vote, investment_in_accepting_budget) } - it { should be_able_to(:vote, investment_in_selecting_budget) } - it { should_not be_able_to(:vote, investment_in_balloting_budget) } + describe "Poll" do + it { should be_able_to(:answer, current_poll) } + it { should_not be_able_to(:answer, expired_poll) } + it { should_not be_able_to(:answer, incoming_poll) } - it { should_not be_able_to(:destroy, investment_in_accepting_budget) } - it { should_not be_able_to(:destroy, investment_in_reviewing_budget) } - it { should_not be_able_to(:destroy, investment_in_selecting_budget) } - it { should_not be_able_to(:destroy, investment_in_balloting_budget) } + it { should be_able_to(:answer, poll_question_from_own_geozone) } + it { should be_able_to(:answer, poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, poll_question_from_other_geozone) } - it { should be_able_to(:destroy, own_investment_in_accepting_budget) } - it { should be_able_to(:destroy, own_investment_in_reviewing_budget) } - it { should_not be_able_to(:destroy, own_investment_in_selecting_budget) } - it { should_not be_able_to(:destroy, investment_in_balloting_budget) } + it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) } - it { should_not be_able_to(:create, ballot_in_accepting_budget) } - it { should_not be_able_to(:create, ballot_in_selecting_budget) } - it { should be_able_to(:create, ballot_in_balloting_budget) } + it { should_not be_able_to(:answer, incoming_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone) } - it { should be_able_to(:new, DirectMessage) } - it { should be_able_to(:create, DirectMessage) } - it { should be_able_to(:show, own_direct_message) } - it { should_not be_able_to(:show, create(:direct_message)) } + context "without geozone" do + before(:each) { user.geozone = nil } + + it { should_not be_able_to(:answer, poll_question_from_own_geozone) } + it { should be_able_to(:answer, poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, poll_question_from_other_geozone) } + + it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) } + + it { should_not be_able_to(:answer, incoming_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone) } + end + end + + describe "Budgets" do + it { should be_able_to(:create, investment_in_accepting_budget) } + it { should_not be_able_to(:create, investment_in_selecting_budget) } + it { should_not be_able_to(:create, investment_in_balloting_budget) } + + it { should_not be_able_to(:vote, investment_in_accepting_budget) } + it { should be_able_to(:vote, investment_in_selecting_budget) } + it { should_not be_able_to(:vote, investment_in_balloting_budget) } + + it { should_not be_able_to(:destroy, investment_in_accepting_budget) } + it { should_not be_able_to(:destroy, investment_in_reviewing_budget) } + it { should_not be_able_to(:destroy, investment_in_selecting_budget) } + it { should_not be_able_to(:destroy, investment_in_balloting_budget) } + + it { should be_able_to(:destroy, own_investment_in_accepting_budget) } + it { should be_able_to(:destroy, own_investment_in_reviewing_budget) } + it { should_not be_able_to(:destroy, own_investment_in_selecting_budget) } + it { should_not be_able_to(:destroy, investment_in_balloting_budget) } + + it { should_not be_able_to(:create, ballot_in_accepting_budget) } + it { should_not be_able_to(:create, ballot_in_selecting_budget) } + it { should be_able_to(:create, ballot_in_balloting_budget) } + end end describe "when level 3 verified" do @@ -148,16 +219,47 @@ describe "Abilities::Common" do let(:own_direct_message) { create(:direct_message, sender: user) } before{ user.update(verified_at: Time.current) } - it { should be_able_to(:vote, Proposal) } + it { should be_able_to(:vote, Proposal) } it { should be_able_to(:vote_featured, Proposal) } - it { should be_able_to(:create, SpendingProposal) } + it { should be_able_to(:create, SpendingProposal) } it { should_not be_able_to(:destroy, create(:spending_proposal)) } - it { should_not be_able_to(:destroy, own_spending_proposal) } + it { should_not be_able_to(:destroy, own_spending_proposal) } - it { should be_able_to(:new, DirectMessage) } - it { should be_able_to(:create, DirectMessage) } - it { should be_able_to(:show, own_direct_message) } + it { should be_able_to(:new, DirectMessage) } + it { should be_able_to(:create, DirectMessage) } + it { should be_able_to(:show, own_direct_message) } it { should_not be_able_to(:show, create(:direct_message)) } + + it { should be_able_to(:answer, current_poll) } + it { should_not be_able_to(:answer, expired_poll) } + it { should_not be_able_to(:answer, incoming_poll) } + + it { should be_able_to(:answer, poll_question_from_own_geozone) } + it { should be_able_to(:answer, poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, poll_question_from_other_geozone) } + + it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) } + + it { should_not be_able_to(:answer, incoming_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone) } + + context "without geozone" do + before(:each) { user.geozone = nil } + it { should_not be_able_to(:answer, poll_question_from_own_geozone) } + it { should be_able_to(:answer, poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, poll_question_from_other_geozone) } + + it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) } + + it { should_not be_able_to(:answer, incoming_poll_question_from_own_geozone) } + it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones) } + it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone) } + end end end diff --git a/spec/models/activity_spec.rb b/spec/models/activity_spec.rb index 0c7b99027..44ecc6ffd 100644 --- a/spec/models/activity_spec.rb +++ b/spec/models/activity_spec.rb @@ -56,11 +56,12 @@ describe Activity do activity3 = create(:activity, user: user1, action: "hide", actionable: create(:proposal)) activity4 = create(:activity, user: user1, action: "hide", actionable: create(:comment)) activity5 = create(:activity, user: user1, action: "block", actionable: create(:user)) + activity6 = create(:activity, user: user1, action: "valuate", actionable: create(:budget_investment)) create_list(:activity, 3) - expect(Activity.by(user1).size).to eq 5 + expect(Activity.by(user1).size).to eq 6 - [activity1, activity2, activity3, activity4, activity5].each do |a| + [activity1, activity2, activity3, activity4, activity5, activity6].each do |a| expect(Activity.by(user1)).to include(a) end end @@ -68,20 +69,23 @@ describe Activity do describe "scopes by actionable" do it "should filter by actionable type" do - on_proposal = create(:activity, actionable: create(:proposal)) - on_debate = create(:activity, actionable: create(:debate)) - on_comment = create(:activity, actionable: create(:comment)) - on_user = create(:activity, actionable: create(:user)) + on_proposal = create(:activity, actionable: create(:proposal)) + on_debate = create(:activity, actionable: create(:debate)) + on_comment = create(:activity, actionable: create(:comment)) + on_user = create(:activity, actionable: create(:user)) + on_investment = create(:activity, actionable: create(:budget_investment)) expect(Activity.on_proposals.size).to eq 1 expect(Activity.on_debates.size).to eq 1 expect(Activity.on_comments.size).to eq 1 expect(Activity.on_users.size).to eq 1 + expect(Activity.on_budget_investments.size).to eq 1 expect(Activity.on_proposals.first).to eq on_proposal expect(Activity.on_debates.first).to eq on_debate expect(Activity.on_comments.first).to eq on_comment expect(Activity.on_users.first).to eq on_user + expect(Activity.on_budget_investments.first).to eq on_investment end end diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb index abbc8ad26..fa51f3cab 100644 --- a/spec/models/budget/investment_spec.rb +++ b/spec/models/budget/investment_spec.rb @@ -35,7 +35,26 @@ describe Budget::Investment do expect(investment.description).to eq("alert('danger');") end - describe "#unfeasibility_explanation" do + it "set correct group and budget ids" do + budget = create(:budget) + group_1 = create(:budget_group, budget: budget) + group_2 = create(:budget_group, budget: budget) + + heading_1 = create(:budget_heading, group: group_1) + heading_2 = create(:budget_heading, group: group_2) + + investment = create(:budget_investment, heading: heading_1) + + expect(investment.budget_id).to eq budget.id + expect(investment.group_id).to eq group_1.id + + investment.update(heading: heading_2) + + expect(investment.budget_id).to eq budget.id + expect(investment.group_id).to eq group_2.id + end + + describe "#unfeasibility_explanation blank" do it "should be valid if valuation not finished" do investment.unfeasibility_explanation = "" investment.valuation_finished = false @@ -57,6 +76,29 @@ describe Budget::Investment do end end + describe "#price blank" do + it "should be valid if valuation not finished" do + investment.price = "" + investment.valuation_finished = false + expect(investment).to be_valid + end + + it "should be valid if valuation finished and unfeasible" do + investment.price = "" + investment.unfeasibility_explanation = "reason" + investment.feasibility = "unfeasible" + investment.valuation_finished = true + expect(investment).to be_valid + end + + it "should not be valid if valuation finished and feasible" do + investment.price = "" + investment.feasibility = "feasible" + investment.valuation_finished = true + expect(investment).to_not be_valid + end + end + describe "#code" do let(:investment) { create(:budget_investment) } @@ -285,6 +327,45 @@ describe Budget::Investment do expect(salamanca_investment.valid_heading?(user)).to eq(false) end + + it "allows votes in a group with a single heading" do + all_city_investment = create(:budget_investment, heading: heading) + expect(all_city_investment.valid_heading?(user)).to eq(true) + end + + it "allows votes in a group with a single heading after voting in that heading" do + all_city_investment1 = create(:budget_investment, heading: heading) + all_city_investment2 = create(:budget_investment, heading: heading) + + create(:vote, votable: all_city_investment1, voter: user) + + expect(all_city_investment2.valid_heading?(user)).to eq(true) + end + + it "allows votes in a group with a single heading after voting in another group" do + districts = create(:budget_group, budget: budget) + carabanchel = create(:budget_heading, group: districts) + + all_city_investment = create(:budget_investment, heading: heading) + carabanchel_investment = create(:budget_investment, heading: carabanchel) + + create(:vote, votable: carabanchel_investment, voter: user) + + expect(all_city_investment.valid_heading?(user)).to eq(true) + end + + it "allows votes in a group with multiple headings after voting in group with a single heading" do + districts = create(:budget_group, budget: budget) + carabanchel = create(:budget_heading, group: districts) + salamanca = create(:budget_heading, group: districts) + + all_city_investment = create(:budget_investment, heading: heading) + carabanchel_investment = create(:budget_investment, heading: carabanchel) + + create(:vote, votable: all_city_investment, voter: user) + + expect(carabanchel_investment.valid_heading?(user)).to eq(true) + end end end diff --git a/spec/models/officing/residence_spec.rb b/spec/models/officing/residence_spec.rb new file mode 100644 index 000000000..dee63b85a --- /dev/null +++ b/spec/models/officing/residence_spec.rb @@ -0,0 +1,121 @@ +require 'rails_helper' + +describe Officing::Residence do + + let!(:geozone) { create(:geozone, census_code: "01") } + let(:residence) { build(:officing_residence, document_number: "12345678Z") } + + describe "validations" do + + it "should be valid" do + expect(residence).to be_valid + end + + it "should not be valid without a document number" do + residence.document_number = nil + expect(residence).to_not be_valid + end + + it "should not be valid without a document type" do + residence.document_type = nil + expect(residence).to_not be_valid + end + + it "should not be valid without a year of birth" do + residence.year_of_birth = nil + expect(residence).to_not be_valid + end + + describe "allowed age" do + it "should not be valid if user is under allowed age" do + allow_any_instance_of(Officing::Residence).to receive(:date_of_birth).and_return(15.years.ago) + expect(residence).to_not be_valid + expect(residence.errors[:year_of_birth]).to include("You don't have the required age to participate") + end + + it "should be valid if user is above allowed age" do + allow_any_instance_of(Officing::Residence).to receive(:date_of_birth).and_return(16.years.ago) + expect(residence).to be_valid + expect(residence.errors[:year_of_birth]).to be_empty + end + end + + end + + describe "new" do + it "should upcase document number" do + residence = Officing::Residence.new({document_number: "x1234567z"}) + expect(residence.document_number).to eq("X1234567Z") + end + + it "should remove all characters except numbers and letters" do + residence = Officing::Residence.new({document_number: " 12.345.678 - B"}) + expect(residence.document_number).to eq("12345678B") + end + end + + describe "save" do + + it "should store document number, document type, geozone, date of birth and gender" do + residence.save + user = residence.user + + expect(user.document_number).to eq('12345678Z') + expect(user.document_type).to eq("1") + expect(user.date_of_birth.year).to eq(1980) + expect(user.date_of_birth.month).to eq(12) + expect(user.date_of_birth.day).to eq(31) + expect(user.gender).to eq('male') + expect(user.geozone).to eq(geozone) + end + + it "should find existing user and use demographic information" do + geozone = create(:geozone) + create(:user, document_number: "12345678Z", + document_type: "1", + date_of_birth: Date.new(1981, 11, 30), + gender: 'female', + geozone: geozone) + + residence = build(:officing_residence, + document_number: "12345678Z", + document_type: "1") + + residence.save + user = residence.user + + expect(user.document_number).to eq('12345678Z') + expect(user.document_type).to eq("1") + expect(user.date_of_birth.year).to eq(1981) + expect(user.date_of_birth.month).to eq(11) + expect(user.date_of_birth.day).to eq(30) + expect(user.gender).to eq('female') + expect(user.geozone).to eq(geozone) + end + + it "makes half-verified users fully verified" do + user = create(:user, residence_verified_at: Time.current, document_type: "1", document_number: "12345678Z") + expect(user).to be_unverified + residence = build(:officing_residence, document_number: "12345678Z", year_of_birth: 1980) + expect(residence).to be_valid + expect(user.reload).to be_unverified + residence.save + expect(user.reload).to be_level_three_verified + end + + it "stores failed census calls" do + residence = build(:officing_residence, :invalid, document_number: "12345678Z") + residence.save + + expect(FailedCensusCall.count).to eq(1) + expect(FailedCensusCall.first).to have_attributes({ + user_id: residence.user.id, + poll_officer_id: residence.officer.id, + document_number: "12345678Z", + document_type: "1", + year_of_birth: Time.current.year + }) + end + + end +end diff --git a/spec/models/poll/answer_spec.rb b/spec/models/poll/answer_spec.rb new file mode 100644 index 000000000..10ccafbb6 --- /dev/null +++ b/spec/models/poll/answer_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +describe Poll::Answer do + + describe "validations" do + it "validates that the answers are included in the Poll::Question's list" do + q = create(:poll_question, valid_answers: 'One, Two, Three') + expect(build(:poll_answer, question: q, answer: 'One')).to be_valid + expect(build(:poll_answer, question: q, answer: 'Two')).to be_valid + expect(build(:poll_answer, question: q, answer: 'Three')).to be_valid + + expect(build(:poll_answer, question: q, answer: 'Four')).to_not be_valid + end + end + + describe "#record_voter_participation" do + it "creates a poll_voter with user and poll data" do + answer = create(:poll_answer) + expect(answer.poll.voters).to be_blank + + answer.record_voter_participation + expect(answer.poll.reload.voters.size).to eq(1) + voter = answer.poll.voters.first + + expect(voter.document_number).to eq(answer.author.document_number) + expect(voter.poll_id).to eq(answer.poll.id) + end + end + +end diff --git a/spec/models/poll/booth_spec.rb b/spec/models/poll/booth_spec.rb new file mode 100644 index 000000000..c095c62cd --- /dev/null +++ b/spec/models/poll/booth_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe :booth do + + let(:booth) { build(:poll_booth) } + + it "should be valid" do + expect(booth).to be_valid + end + + it "should not be valid without a name" do + booth.name = nil + expect(booth).to_not be_valid + end + + describe "#search" do + it "should find booths searching by name or location" do + booth1 = create(:poll_booth, name: "Booth number 1", location: "City center") + booth2 = create(:poll_booth, name: "Central", location: "Town hall") + + expect(Poll::Booth.search("number")).to eq([booth1]) + expect(Poll::Booth.search("hall")).to eq([booth2]) + expect(Poll::Booth.search("cen").size).to eq 2 + end + 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..bccd772ca --- /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 = create(:poll_officer_assignment, id: 111) + final_recount.save + + final_recount.count = 32 + final_recount.officer_assignment = create(:poll_officer_assignment, id: 112) + final_recount.save + + final_recount.count = 34 + final_recount.officer_assignment = create(:poll_officer_assignment, id: 113) + final_recount.save + + expect(final_recount.officer_assignment_id_log).to eq(":111:112") + end + +end \ No newline at end of file diff --git a/spec/models/poll/null_result_spec.rb b/spec/models/poll/null_result_spec.rb new file mode 100644 index 000000000..746b48377 --- /dev/null +++ b/spec/models/poll/null_result_spec.rb @@ -0,0 +1,70 @@ +require 'rails_helper' + +describe Poll::NullResult do + + describe "logging changes" do + it "should update amount_log if amount changes" do + null_result = create(:poll_null_result, amount: 33) + + expect(null_result.amount_log).to eq("") + + null_result.amount = 33 + null_result.save + null_result.amount = 32 + null_result.save + null_result.amount = 34 + null_result.save + + expect(null_result.amount_log).to eq(":33:32") + end + + it "should update officer_assignment_id_log if amount changes" do + null_result = create(:poll_null_result, amount: 33) + + expect(null_result.amount_log).to eq("") + expect(null_result.officer_assignment_id_log).to eq("") + + null_result.amount = 33 + null_result.officer_assignment = create(:poll_officer_assignment, id: 101) + null_result.save + + null_result.amount = 32 + null_result.officer_assignment = create(:poll_officer_assignment, id: 102) + null_result.save + + null_result.amount = 34 + null_result.officer_assignment = create(:poll_officer_assignment, id: 103) + null_result.save + + expect(null_result.amount_log).to eq(":33:32") + expect(null_result.officer_assignment_id_log).to eq(":101:102") + end + + it "should update author_id if amount changes" do + null_result = create(:poll_null_result, amount: 33) + + expect(null_result.amount_log).to eq("") + expect(null_result.author_id_log).to eq("") + + author_A = create(:poll_officer).user + author_B = create(:poll_officer).user + author_C = create(:poll_officer).user + + null_result.amount = 33 + null_result.author_id = author_A.id + null_result.save! + + null_result.amount = 32 + null_result.author_id = author_B.id + null_result.save! + + null_result.amount = 34 + null_result.author_id = author_C.id + null_result.save! + + expect(null_result.amount_log).to eq(":33:32") + expect(null_result.author_id_log).to eq(":#{author_A.id}:#{author_B.id}") + end + end + +end \ No newline at end of file diff --git a/spec/models/poll/officer_assignment_spec.rb b/spec/models/poll/officer_assignment_spec.rb new file mode 100644 index 000000000..b6bce5481 --- /dev/null +++ b/spec/models/poll/officer_assignment_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +describe :officer_assignment do + it "should log user data on creation" do + user = create(:user, username: "Larry Bird", email: "larry@lege.nd") + officer = create(:poll_officer, user: user) + + oa = create(:poll_officer_assignment, officer: officer) + + expect(oa.reload.user_data_log).to eq "#{user.id} - Larry Bird (larry@lege.nd)" + end +end \ No newline at end of file diff --git a/spec/models/poll/officer_spec.rb b/spec/models/poll/officer_spec.rb new file mode 100644 index 000000000..83bdbecc2 --- /dev/null +++ b/spec/models/poll/officer_spec.rb @@ -0,0 +1,124 @@ +require 'rails_helper' + +describe :officer do + + describe "#voting_days_assigned_polls" do + it "should return all polls with this officer assigned during voting days" do + officer = create(:poll_officer) + + poll_1 = create(:poll) + poll_2 = create(:poll) + poll_3 = create(:poll) + + booth_assignment_1a = create(:poll_booth_assignment, poll: poll_1) + booth_assignment_1b = create(:poll_booth_assignment, poll: poll_1) + booth_assignment_2 = create(:poll_booth_assignment, poll: poll_2) + + create(:poll_officer_assignment, booth_assignment: booth_assignment_1a, officer: officer, date: poll_1.starts_at) + create(:poll_officer_assignment, booth_assignment: booth_assignment_1b, officer: officer, date: poll_1.ends_at) + create(:poll_officer_assignment, booth_assignment: booth_assignment_2, officer: officer) + + assigned_polls = officer.voting_days_assigned_polls + expect(assigned_polls.size).to eq 2 + expect(assigned_polls.include?(poll_1)).to eq(true) + expect(assigned_polls.include?(poll_2)).to eq(true) + expect(assigned_polls.include?(poll_3)).to eq(false) + end + + it "should not return polls with this officer assigned for final recount/results" do + officer = create(:poll_officer) + + poll_1 = create(:poll) + poll_2 = create(:poll) + + booth_assignment_1 = create(:poll_booth_assignment, poll: poll_1) + booth_assignment_2 = create(:poll_booth_assignment, poll: poll_2) + + create(:poll_officer_assignment, booth_assignment: booth_assignment_1, officer: officer, date: poll_1.starts_at) + create(:poll_officer_assignment, booth_assignment: booth_assignment_2, officer: officer, final: true) + + assigned_polls = officer.voting_days_assigned_polls + expect(assigned_polls.size).to eq 1 + expect(assigned_polls.include?(poll_1)).to eq(true) + expect(assigned_polls.include?(poll_2)).to eq(false) + end + + it "should return polls ordered by end date (desc)" do + officer = create(:poll_officer) + + poll_1 = create(:poll, ends_at: 1.day.ago) + poll_2 = create(:poll, ends_at: 10.days.from_now) + poll_3 = create(:poll, ends_at: 10.day.ago) + + [poll_1, poll_2, poll_3].each do |p| + create(:poll_officer_assignment, officer: officer, booth_assignment: create(:poll_booth_assignment, poll: p)) + end + + assigned_polls = officer.voting_days_assigned_polls + + expect(assigned_polls.first).to eq(poll_2) + expect(assigned_polls.second).to eq(poll_1) + expect(assigned_polls.last).to eq(poll_3) + end + end + + describe "#final_days_assigned_polls" do + it "should return all polls with this officer assigned for final recount/results" do + officer = create(:poll_officer) + + poll_1 = create(:poll) + poll_2 = create(:poll) + poll_3 = create(:poll) + + booth_assignment_1a = create(:poll_booth_assignment, poll: poll_1) + booth_assignment_1b = create(:poll_booth_assignment, poll: poll_1) + booth_assignment_2 = create(:poll_booth_assignment, poll: poll_2) + + create(:poll_officer_assignment, booth_assignment: booth_assignment_1a, officer: officer, date: poll_1.starts_at, final: true) + create(:poll_officer_assignment, booth_assignment: booth_assignment_1b, officer: officer, date: poll_1.ends_at, final: true) + create(:poll_officer_assignment, booth_assignment: booth_assignment_2, officer: officer, final: true) + + assigned_polls = officer.final_days_assigned_polls + expect(assigned_polls.size).to eq 2 + expect(assigned_polls.include?(poll_1)).to eq(true) + expect(assigned_polls.include?(poll_2)).to eq(true) + expect(assigned_polls.include?(poll_3)).to eq(false) + end + + it "should not return polls with this officer assigned for voting days" do + officer = create(:poll_officer) + + poll_1 = create(:poll) + poll_2 = create(:poll) + + booth_assignment_1 = create(:poll_booth_assignment, poll: poll_1) + booth_assignment_2 = create(:poll_booth_assignment, poll: poll_2) + + create(:poll_officer_assignment, booth_assignment: booth_assignment_1, officer: officer, date: poll_1.starts_at) + create(:poll_officer_assignment, booth_assignment: booth_assignment_2, officer: officer, final: true) + + assigned_polls = officer.final_days_assigned_polls + expect(assigned_polls.size).to eq 1 + expect(assigned_polls.include?(poll_1)).to eq(false) + expect(assigned_polls.include?(poll_2)).to eq(true) + end + + it "should return polls ordered by end date (desc)" do + officer = create(:poll_officer) + + poll_1 = create(:poll, ends_at: 1.day.ago) + poll_2 = create(:poll, ends_at: 10.days.from_now) + poll_3 = create(:poll, ends_at: 10.day.ago) + + [poll_1, poll_2, poll_3].each do |p| + create(:poll_officer_assignment, officer: officer, booth_assignment: create(:poll_booth_assignment, poll: p), final: true) + end + + assigned_polls = officer.final_days_assigned_polls + + expect(assigned_polls.first).to eq(poll_2) + expect(assigned_polls.second).to eq(poll_1) + expect(assigned_polls.last).to eq(poll_3) + end + end +end \ No newline at end of file diff --git a/spec/models/poll/partial_result_spec.rb b/spec/models/poll/partial_result_spec.rb new file mode 100644 index 000000000..dd6176a06 --- /dev/null +++ b/spec/models/poll/partial_result_spec.rb @@ -0,0 +1,81 @@ +require 'rails_helper' + +describe Poll::PartialResult do + + describe "validations" do + it "validates that the answers are included in the Poll::Question's list" do + q = create(:poll_question, valid_answers: 'One, Two, Three') + expect(build(:poll_partial_result, question: q, answer: 'One')).to be_valid + expect(build(:poll_partial_result, question: q, answer: 'Two')).to be_valid + expect(build(:poll_partial_result, question: q, answer: 'Three')).to be_valid + + expect(build(:poll_partial_result, question: q, answer: 'Four')).to_not be_valid + end + end + + describe "logging changes" do + it "should update amount_log if amount changes" do + partial_result = create(:poll_partial_result, amount: 33) + + expect(partial_result.amount_log).to eq("") + + partial_result.amount = 33 + partial_result.save + partial_result.amount = 32 + partial_result.save + partial_result.amount = 34 + partial_result.save + + expect(partial_result.amount_log).to eq(":33:32") + end + + it "should update officer_assignment_id_log if amount changes" do + partial_result = create(:poll_partial_result, amount: 33) + + expect(partial_result.amount_log).to eq("") + expect(partial_result.officer_assignment_id_log).to eq("") + + partial_result.amount = 33 + partial_result.officer_assignment = create(:poll_officer_assignment, id: 10) + partial_result.save + + partial_result.amount = 32 + partial_result.officer_assignment = create(:poll_officer_assignment, id: 20) + partial_result.save + + partial_result.amount = 34 + partial_result.officer_assignment = create(:poll_officer_assignment, id: 30) + partial_result.save + + expect(partial_result.amount_log).to eq(":33:32") + expect(partial_result.officer_assignment_id_log).to eq(":10:20") + end + + it "should update author_id if amount changes" do + partial_result = create(:poll_partial_result, amount: 33) + + expect(partial_result.amount_log).to eq("") + expect(partial_result.author_id_log).to eq("") + + author_A = create(:poll_officer).user + author_B = create(:poll_officer).user + author_C = create(:poll_officer).user + + partial_result.amount = 33 + partial_result.author_id = author_A.id + partial_result.save! + + partial_result.amount = 32 + partial_result.author_id = author_B.id + partial_result.save! + + partial_result.amount = 34 + partial_result.author_id = author_C.id + partial_result.save! + + expect(partial_result.amount_log).to eq(":33:32") + expect(partial_result.author_id_log).to eq(":#{author_A.id}:#{author_B.id}") + end + end + +end diff --git a/spec/models/poll/poll_spec.rb b/spec/models/poll/poll_spec.rb new file mode 100644 index 000000000..80a57e916 --- /dev/null +++ b/spec/models/poll/poll_spec.rb @@ -0,0 +1,143 @@ +require 'rails_helper' + +describe :poll do + + let(:poll) { build(:poll) } + + describe "validations" do + it "should be valid" do + expect(poll).to be_valid + end + + it "should not be valid without a name" do + poll.name = nil + expect(poll).to_not be_valid + end + + it "should not be valid without a start date" do + poll.starts_at = nil + expect(poll).to_not be_valid + end + + it "should not be valid without an end date" do + poll.ends_at = nil + expect(poll).to_not be_valid + end + + it "should not be valid without a proper start/end date range" do + poll.starts_at = 1.week.ago + poll.ends_at = 2.months.ago + expect(poll).to_not be_valid + end + end + + describe "#opened?" do + it "returns true only when it isn't too early or too late" do + expect(create(:poll, :incoming)).to_not be_current + expect(create(:poll, :expired)).to_not be_current + expect(create(:poll)).to be_current + end + end + + describe "#incoming?" do + it "returns true only when it is too early" do + expect(create(:poll, :incoming)).to be_incoming + expect(create(:poll, :expired)).to_not be_incoming + expect(create(:poll)).to_not be_incoming + end + end + + describe "#expired?" do + it "returns true only when it is too late" do + expect(create(:poll, :incoming)).to_not be_expired + expect(create(:poll, :expired)).to be_expired + expect(create(:poll)).to_not be_expired + end + end + + describe "#published?" do + it "returns true only when published is true" do + expect(create(:poll)).to_not be_published + expect(create(:poll, :published)).to be_published + end + end + + describe "#document_has_voted?" do + it "returns true if Poll::Voter with document exists" do + poll = create(:poll) + voter = create(:poll_voter, :valid_document, poll: poll) + + expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(true) + end + + it "returns false if Poll::Voter with document does not exists" do + poll_2 = create(:poll) + voter = create(:poll_voter, :valid_document, poll: poll_2) + + expect(poll.document_has_voted?(voter.document_number, voter.document_type)).to eq(false) + end + end + + describe "answerable_by" do + let(:geozone) {create(:geozone) } + + let!(:current_poll) { create(:poll) } + let!(:expired_poll) { create(:poll, :expired) } + let!(:incoming_poll) { create(:poll, :incoming) } + let!(:current_restricted_poll) { create(:poll, geozone_restricted: true, geozones: [geozone]) } + let!(:expired_restricted_poll) { create(:poll, :expired, geozone_restricted: true, geozones: [geozone]) } + let!(:incoming_restricted_poll) { create(:poll, :incoming, geozone_restricted: true, geozones: [geozone]) } + let!(:all_polls) { [current_poll, expired_poll, incoming_poll, current_poll, expired_restricted_poll, incoming_restricted_poll] } + let(:non_current_polls) { [expired_poll, incoming_poll, expired_restricted_poll, incoming_restricted_poll] } + + let(:non_user) { nil } + let(:level1) { create(:user) } + let(:level2) { create(:user, :level_two) } + let(:level2_from_geozone) { create(:user, :level_two, geozone: geozone) } + let(:all_users) { [non_user, level1, level2, level2_from_geozone] } + + describe 'instance method' do + it "rejects non-users and level 1 users" do + all_polls.each do |poll| + expect(poll).to_not be_answerable_by(non_user) + expect(poll).to_not be_answerable_by(level1) + end + end + + it "rejects everyone when not current" do + non_current_polls.each do |poll| + all_users.each do |user| + expect(poll).to_not be_answerable_by(user) + end + end + end + + it "accepts level 2 users when unrestricted and current" do + expect(current_poll).to be_answerable_by(level2) + expect(current_poll).to be_answerable_by(level2_from_geozone) + end + + it "accepts level 2 users only from the same geozone when restricted by geozone" do + expect(current_restricted_poll).to_not be_answerable_by(level2) + expect(current_restricted_poll).to be_answerable_by(level2_from_geozone) + end + end + + describe 'class method' do + it "returns no polls for non-users and level 1 users" do + expect(Poll.answerable_by(nil)).to be_empty + expect(Poll.answerable_by(level1)).to be_empty + end + + it "returns unrestricted polls for level 2 users" do + expect(Poll.answerable_by(level2).to_a).to eq([current_poll]) + end + + it "returns restricted & unrestricted polls for level 2 users of the correct geozone" do + list = Poll.answerable_by(level2_from_geozone) + .order(:geozone_restricted) + expect(list.to_a).to eq([current_poll, current_restricted_poll]) + end + end + end +end diff --git a/spec/models/poll/question_spec.rb b/spec/models/poll/question_spec.rb new file mode 100644 index 000000000..057dfebca --- /dev/null +++ b/spec/models/poll/question_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +RSpec.describe Poll::Question, type: :model do + + describe "#valid_answers" do + it "gets a comma-separated string, but returns an array" do + q = create(:poll_question, valid_answers: "Yes, No") + expect(q.valid_answers).to eq(["Yes", "No"]) + end + end + + describe "#copy_attributes_from_proposal" do + it "copies the attributes from the proposal" do + create_list(:geozone, 3) + p = create(:proposal) + q = create(:poll_question) + q.copy_attributes_from_proposal(p) + expect(q.valid_answers).to eq(['Yes', 'No']) + expect(q.author).to eq(p.author) + expect(q.author_visible_name).to eq(p.author.name) + expect(q.proposal_id).to eq(p.id) + expect(q.title).to eq(p.title) + end + end + +end diff --git a/spec/models/poll/recount_spec.rb b/spec/models/poll/recount_spec.rb new file mode 100644 index 000000000..fb2c80704 --- /dev/null +++ b/spec/models/poll/recount_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +describe :recount do + + it "should update count_log if count changes" do + recount = create(:poll_recount, count: 33) + + expect(recount.count_log).to eq("") + + recount.count = 33 + recount.save + recount.count = 32 + recount.save + recount.count = 34 + recount.save + + expect(recount.count_log).to eq(":33:32") + end + + it "should update officer_assignment_id_log if count changes" do + recount = create(:poll_recount, count: 33) + + expect(recount.count_log).to eq("") + + recount.count = 33 + recount.officer_assignment = create(:poll_officer_assignment, id: 11) + recount.save + + recount.count = 32 + recount.officer_assignment = create(:poll_officer_assignment, id: 12) + recount.save + + recount.count = 34 + recount.officer_assignment = create(:poll_officer_assignment, id: 13) + recount.save + + expect(recount.officer_assignment_id_log).to eq(":11:12") + end + +end \ No newline at end of file diff --git a/spec/models/poll/voter_spec.rb b/spec/models/poll/voter_spec.rb new file mode 100644 index 000000000..752ac5bbc --- /dev/null +++ b/spec/models/poll/voter_spec.rb @@ -0,0 +1,114 @@ +require 'rails_helper' + +describe :voter do + + let(:poll) { create(:poll) } + let(:booth) { create(:poll_booth) } + let(:booth_assignment) { create(:poll_booth_assignment, poll: poll, booth: booth) } + let(:voter) { create(:poll_voter) } + + describe "validations" do + + it "should be valid" do + expect(voter).to be_valid + end + + it "should not be valid without a user" do + voter.user = nil + expect(voter).to_not be_valid + end + + it "should not be valid without a poll" do + voter.poll = nil + expect(voter).to_not be_valid + end + + it "should be valid if has not voted" do + voter = build(:poll_voter, :valid_document) + + expect(voter).to be_valid + end + + it "should not be valid if the user has already voted in the same poll or booth_assignment" do + user = create(:user, :level_two) + + voter1 = create(:poll_voter, user: user, poll: poll) + voter2 = build(:poll_voter, user: user, poll: poll) + + expect(voter2).to_not be_valid + expect(voter2.errors.messages[:document_number]).to eq(["User has already voted"]) + end + + it "should not be valid if the user has already voted in the same poll/booth" do + user = create(:user, :level_two) + + voter1 = create(:poll_voter, user: user, poll: poll, booth_assignment: booth_assignment) + voter2 = build(:poll_voter, user: user, poll: poll, booth_assignment: booth_assignment) + + expect(voter2).to_not be_valid + expect(voter2.errors.messages[:document_number]).to eq(["User has already voted"]) + end + + it "should not be valid if the user has already voted in different booth in the same poll" do + booth_assignment1 = create(:poll_booth_assignment, poll: poll) + booth_assignment2 = create(:poll_booth_assignment, poll: poll) + + user = create(:user, :level_two) + + voter1 = create(:poll_voter, user: user, poll: poll, booth_assignment: booth_assignment1) + voter2 = build(:poll_voter, user: user, poll: poll, booth_assignment: booth_assignment2) + + expect(voter2).to_not be_valid + expect(voter2.errors.messages[:document_number]).to eq(["User has already voted"]) + end + + it "should be valid if the user has already voted in the same booth in different poll" do + booth_assignment1 = create(:poll_booth_assignment, booth: booth) + booth_assignment2 = create(:poll_booth_assignment, booth: booth, poll: poll) + + user = create(:user, :level_two) + + voter1 = create(:poll_voter, user: user, booth_assignment: booth_assignment1) + voter2 = build(:poll_voter, user: user, booth_assignment: booth_assignment2) + + expect(voter2).to be_valid + end + + it "should not be valid if the user has voted via web" do + answer = create(:poll_answer) + answer.record_voter_participation + + voter = build(:poll_voter, poll: answer.question.poll, user: answer.author) + expect(voter).to_not be_valid + expect(voter.errors.messages[:document_number]).to eq(["User has already voted"]) + end + + end + + describe "save" do + + it "sets demographic info" do + geozone = create(:geozone) + user = create(:user, + geozone: geozone, + date_of_birth: 30.years.ago, + gender: "female") + + voter = build(:poll_voter, user: user) + voter.save + + expect(voter.geozone).to eq(geozone) + expect(voter.age).to eq(30) + expect(voter.gender).to eq("female") + end + + it "sets user info" do + user = create(:user, document_number: "1234A", document_type: "1") + voter = build(:poll_voter, user: user) + voter.save + + expect(voter.document_number).to eq("1234A") + expect(voter.document_type).to eq("1") + end + end +end \ No newline at end of file diff --git a/spec/models/poll/white_result_spec.rb b/spec/models/poll/white_result_spec.rb new file mode 100644 index 000000000..687c4ebe1 --- /dev/null +++ b/spec/models/poll/white_result_spec.rb @@ -0,0 +1,70 @@ +require 'rails_helper' + +describe Poll::WhiteResult do + + describe "logging changes" do + it "should update amount_log if amount changes" do + white_result = create(:poll_white_result, amount: 33) + + expect(white_result.amount_log).to eq("") + + white_result.amount = 33 + white_result.save + white_result.amount = 32 + white_result.save + white_result.amount = 34 + white_result.save + + expect(white_result.amount_log).to eq(":33:32") + end + + it "should update officer_assignment_id_log if amount changes" do + white_result = create(:poll_white_result, amount: 33) + + expect(white_result.amount_log).to eq("") + expect(white_result.officer_assignment_id_log).to eq("") + + white_result.amount = 33 + white_result.officer_assignment = create(:poll_officer_assignment, id: 21) + white_result.save + + white_result.amount = 32 + white_result.officer_assignment = create(:poll_officer_assignment, id: 22) + white_result.save + + white_result.amount = 34 + white_result.officer_assignment = create(:poll_officer_assignment, id: 23) + white_result.save + + expect(white_result.amount_log).to eq(":33:32") + expect(white_result.officer_assignment_id_log).to eq(":21:22") + end + + it "should update author_id if amount changes" do + white_result = create(:poll_white_result, amount: 33) + + expect(white_result.amount_log).to eq("") + expect(white_result.author_id_log).to eq("") + + author_A = create(:poll_officer).user + author_B = create(:poll_officer).user + author_C = create(:poll_officer).user + + white_result.amount = 33 + white_result.author_id = author_A.id + white_result.save! + + white_result.amount = 32 + white_result.author_id = author_B.id + white_result.save! + + white_result.amount = 34 + white_result.author_id = author_C.id + white_result.save! + + expect(white_result.amount_log).to eq(":33:32") + expect(white_result.author_id_log).to eq(":#{author_A.id}:#{author_B.id}") + end + end + +end \ No newline at end of file diff --git a/spec/models/signature_sheet_spec.rb b/spec/models/signature_sheet_spec.rb index 473a7b96d..aeed78996 100644 --- a/spec/models/signature_sheet_spec.rb +++ b/spec/models/signature_sheet_spec.rb @@ -46,12 +46,20 @@ describe SignatureSheet do expect(signature_sheet.name).to eq("Citizen proposal #{proposal.id}") end + it "returns name for spending proposal signature sheets" do spending_proposal = create(:spending_proposal) signature_sheet.signable = spending_proposal expect(signature_sheet.name).to eq("Spending proposal #{spending_proposal.id}") end + + it "returns name for budget investment signature sheets" do + budget_investment = create(:budget_investment) + signature_sheet.signable = budget_investment + + expect(signature_sheet.name).to eq("Investment #{budget_investment.id}") + end end describe "#verify_signatures" do diff --git a/spec/models/signature_spec.rb b/spec/models/signature_spec.rb index 8bb090614..207208bbf 100644 --- a/spec/models/signature_spec.rb +++ b/spec/models/signature_spec.rb @@ -46,49 +46,61 @@ describe Signature do end end - describe "#verified?" do - - it "returns true if user exists" do - user = create(:user, :level_two, document_number: "123A") - signature = create(:signature, document_number: user.document_number) - - expect(signature.verified?).to eq(true) - end - - it "returns true if document number in census" do - signature = create(:signature, document_number: "12345678Z") - - expect(signature.verified?).to eq(true) - end - - it "returns false if user does not exist and not in census" do - signature = create(:signature, document_number: "123A") - - expect(signature.verified?).to eq(false) - end - - end - - describe "#assign_vote" do + describe "#verify" do describe "existing user" do - it "assigns vote to user" do + it "assigns vote to user on proposal" do user = create(:user, :level_two, document_number: "123A") signature = create(:signature, document_number: user.document_number) proposal = signature.signable - signature.assign_vote + signature.verify expect(user.voted_for?(proposal)).to be end + it "assigns vote to user on budget investment" do + investment = create(:budget_investment) + signature_sheet = create(:signature_sheet, signable: investment) + user = create(:user, :level_two, document_number: "123A") + signature = create(:signature, document_number: user.document_number, signature_sheet: signature_sheet) + + signature.verify + + expect(user.voted_for?(investment)).to be + end + it "does not assign vote to user multiple times" do user = create(:user, :level_two, document_number: "123A") signature = create(:signature, document_number: user.document_number) - signature.assign_vote - signature.assign_vote + signature.verify + signature.verify + + expect(Vote.count).to eq(1) + end + + it "does not assigns vote to invalid user on budget investment" do + investment = create(:budget_investment) + signature_sheet = create(:signature_sheet, signable: investment) + user = create(:user, document_number: "123A") + signature = create(:signature, document_number: user.document_number, signature_sheet: signature_sheet) + + signature.verify + + expect(user.voted_for?(investment)).to_not be + expect(Vote.count).to eq(0) + end + + it "does not assign vote to user multiple times on budget investment" do + investment = create(:budget_investment) + signature_sheet = create(:signature_sheet, signable: investment) + user = create(:user, :level_two, document_number: "123A") + signature = create(:signature, document_number: user.document_number, signature_sheet: signature_sheet) + + signature.verify + signature.verify expect(Vote.count).to eq(1) end @@ -100,7 +112,22 @@ describe Signature do signature_sheet = create(:signature_sheet, signable: proposal) signature = create(:signature, signature_sheet: signature_sheet, document_number: user.document_number) - signature.assign_vote + signature.verify + + expect(Vote.count).to eq(1) + end + + it "does not assign vote to user if already voted on budget investment" do + investment = create(:budget_investment) + user = create(:user, :level_two, document_number: "123A") + vote = create(:vote, votable: investment, voter: user) + + signature_sheet = create(:signature_sheet, signable: investment) + signature = create(:signature, document_number: user.document_number, signature_sheet: signature_sheet) + + expect(Vote.count).to eq(1) + + signature.verify expect(Vote.count).to eq(1) end @@ -108,7 +135,7 @@ describe Signature do it "marks the vote as coming from a signature" do signature = create(:signature, document_number: "12345678Z") - signature.assign_vote + signature.verify expect(Vote.last.signature).to eq(signature) end @@ -118,23 +145,27 @@ describe Signature do describe "inexistent user" do it "creates a user with that document number" do + create(:geozone, census_code: "01") signature = create(:signature, document_number: "12345678Z") proposal = signature.signable - signature.assign_vote + signature.verify user = User.last expect(user.document_number).to eq("12345678Z") expect(user.created_from_signature).to eq(true) expect(user.verified_at).to be expect(user.erased_at).to be + expect(user.geozone).to be + expect(user.gender).to be + expect(user.date_of_birth).to be end it "assign the vote to newly created user" do signature = create(:signature, document_number: "12345678Z") proposal = signature.signable - signature.assign_vote + signature.verify user = signature.user expect(user.voted_for?(proposal)).to be @@ -143,22 +174,18 @@ describe Signature do it "assigns signature to vote" do signature = create(:signature, document_number: "12345678Z") - signature.assign_vote + signature.verify expect(Vote.last.signature).to eq(signature) end end - end - - describe "#verify" do - describe "document in census" do - it "calls assign_vote" do + it "calls assign_vote_to_user" do signature = create(:signature, document_number: "12345678Z") - expect(signature).to receive(:assign_vote) + expect(signature).to receive(:assign_vote_to_user) signature.verify end @@ -175,10 +202,10 @@ describe Signature do describe "document not in census" do - it "does not call assign_vote" do + it "does not call assign_vote_to_user" do signature = create(:signature, document_number: "123A") - expect(signature).to_not receive(:assign_vote) + expect(signature).to_not receive(:assign_vote_to_user) signature.verify end diff --git a/spec/models/site_customization/content_block_spec.rb b/spec/models/site_customization/content_block_spec.rb new file mode 100644 index 000000000..2eb6279f6 --- /dev/null +++ b/spec/models/site_customization/content_block_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe SiteCustomization::ContentBlock, type: :model do + let(:block) { build(:site_customization_content_block) } + + it "should be valid" do + expect(block).to be_valid + end + + it "name is unique per locale" do + create(:site_customization_content_block, name: "top_links", locale: "en") + invalid_block = build(:site_customization_content_block, name: "top_links", locale: "en") + + expect(invalid_block).to be_invalid + expect(invalid_block.errors.full_messages).to include("Name has already been taken") + + valid_block = build(:site_customization_content_block, name: "top_links", locale: "es") + expect(valid_block).to be_valid + end +end diff --git a/spec/models/site_customization/page_spec.rb b/spec/models/site_customization/page_spec.rb new file mode 100644 index 000000000..2868f62d1 --- /dev/null +++ b/spec/models/site_customization/page_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +RSpec.describe SiteCustomization::Page, type: :model do + let(:custom_page) { build(:site_customization_page) } + + it "should be valid" do + expect(custom_page).to be_valid + end + + it "is invalid if slug has symbols" do + custom_page = build(:site_customization_page, slug: "as/as*la") + expect(custom_page).to be_invalid + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index ed0bac164..283a01c96 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -65,6 +65,13 @@ describe User do end end + describe "#age" do + it "is the rounded integer age based on the date_of_birth" do + user = create(:user, date_of_birth: 33.years.ago) + expect(user.age).to eq(33) + end + end + describe 'preferences' do describe 'email_on_comment' do it 'should be false by default' do @@ -151,6 +158,18 @@ describe User do end end + describe "poll_officer?" do + it "is false when the user is not a poll officer" do + expect(subject.poll_officer?).to be false + end + + it "is true when the user is a poll officer" do + subject.save + create(:poll_officer, user: subject) + expect(subject.poll_officer?).to be true + end + end + describe "organization?" do it "is false when the user is not an organization" do expect(subject.organization?).to be false @@ -351,6 +370,20 @@ describe User do end end + + describe "erased" do + + it "returns users that have been erased" do + user1 = create(:user, erased_at: Time.current) + user2 = create(:user, erased_at: Time.current) + user3 = create(:user, erased_at: nil) + + expect(User.erased).to include(user1) + expect(User.erased).to include(user2) + expect(User.erased).to_not include(user3) + end + + end end describe "self.search" do @@ -469,4 +502,134 @@ describe User do end + describe "#take_votes_from" do + it "logs info of previous users" do + user = create(:user, :level_three) + other_user = create(:user, :level_three) + another_user = create(:user) + + expect(user.former_users_data_log).to be_blank + + user.take_votes_from other_user + + expect(user.former_users_data_log).to include("id: #{other_user.id}") + + user.take_votes_from another_user + + expect(user.former_users_data_log).to include("id: #{other_user.id}") + expect(user.former_users_data_log).to include("| id: #{another_user.id}") + end + + it "reassigns votes from other user" do + other_user = create(:user, :level_three) + user = create(:user, :level_three) + + v1 = create(:vote, voter: other_user, votable: create(:debate)) + v2 = create(:vote, voter: other_user, votable: create(:proposal)) + v3 = create(:vote, voter: other_user, votable: create(:comment)) + + create(:vote) + + expect(other_user.votes.count).to eq(3) + expect(user.votes.count).to eq(0) + + user.take_votes_from other_user + + expect(other_user.votes.count).to eq(0) + expect(user.vote_ids.sort).to eq([v1.id, v2.id, v3.id].sort) + end + + it "reassigns budget ballots from other user" do + other_user = create(:user, :level_three) + user = create(:user, :level_three) + + b1 = create(:budget_ballot, user: other_user) + b2 = create(:budget_ballot, user: other_user) + + create(:budget_ballot) + + expect(Budget::Ballot.where(user: other_user).count).to eq(2) + expect(Budget::Ballot.where(user: user).count).to eq(0) + + user.take_votes_from other_user + + expect(Budget::Ballot.where(user: other_user).count).to eq(0) + expect(Budget::Ballot.where(user: user).sort).to eq([b1, b2].sort) + end + + it "reassigns poll voters from other user" do + other_user = create(:user, :level_three) + user = create(:user, :level_three) + + v1 = create(:poll_voter, user: other_user) + v2 = create(:poll_voter, user: other_user) + + create(:poll_voter) + + expect(Poll::Voter.where(user: other_user).count).to eq(2) + expect(Poll::Voter.where(user: user).count).to eq(0) + + user.take_votes_from other_user + + expect(Poll::Voter.where(user: other_user).count).to eq(0) + expect(Poll::Voter.where(user: user).sort).to eq([v1, v2].sort) + end + end + + describe "#take_votes_if_erased_document" do + it "does nothing if no erased user with received document" do + user_1 = create(:user, :level_three) + user_2 = create(:user, :level_three) + + create(:vote, voter: user_1) + create(:budget_ballot, user: user_1) + create(:poll_voter, user: user_1) + + user_2.take_votes_if_erased_document(111, 1) + + expect(user_1.votes.count).to eq(1) + expect(user_2.votes.count).to eq(0) + + expect(Budget::Ballot.where(user: user_1).count).to eq(1) + expect(Budget::Ballot.where(user: user_2).count).to eq(0) + + expect(Poll::Voter.where(user: user_1).count).to eq(1) + expect(Poll::Voter.where(user: user_2).count).to eq(0) + end + + it "takes votes from erased user with received document" do + user_1 = create(:user, :level_two, document_number: "12345777", document_type: "1") + user_2 = create(:user) + + create(:vote, voter: user_1) + create(:budget_ballot, user: user_1) + create(:poll_voter, user: user_1) + + user_1.erase + + user_2.take_votes_if_erased_document("12345777", "1") + + expect(user_1.votes.count).to eq(0) + expect(user_2.votes.count).to eq(1) + + expect(Budget::Ballot.where(user: user_1).count).to eq(0) + expect(Budget::Ballot.where(user: user_2).count).to eq(1) + + expect(Poll::Voter.where(user: user_1).count).to eq(0) + expect(Poll::Voter.where(user: user_2).count).to eq(1) + end + + it "removes document from erased user and logs info" do + user_1 = create(:user, document_number: "12345777", document_type: "1") + user_2 = create(:user) + user_1.erase + + user_2.take_votes_if_erased_document("12345777", "1") + + expect(user_2.reload.former_users_data_log).to include("id: #{user_1.id}") + expect(user_1.reload.document_number).to be_blank + end + + end + end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a40934d92..97a94bec7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -105,4 +105,9 @@ RSpec.configure do |config| end # Parallel build helper configuration for travis -Knapsack::Adapters::RSpecAdapter.bind \ No newline at end of file +Knapsack::Adapters::RSpecAdapter.bind + +options = {js_errors: false} +Capybara.register_driver :poltergeist_no_js_errors do |app| + Capybara::Poltergeist::Driver.new(app, options) +end \ No newline at end of file diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index 5cd4e748a..e0fdfe425 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -141,6 +141,16 @@ module CommonActions expect(page).to have_content 'Residence verified' end + def officing_verify_residence + select 'DNI', from: 'residence_document_type' + fill_in 'residence_document_number', with: "12345678Z" + fill_in 'residence_year_of_birth', with: "1980" + + click_button 'Validate document' + + expect(page).to have_content 'Document verified with Census' + end + def confirm_phone fill_in 'sms_phone', with: "611111111" click_button 'Send' @@ -203,7 +213,7 @@ module CommonActions create(:debate, :with_confidence_score, cached_votes_up: 80)] end - def create_successfull_proposals + def create_successful_proposals [create(:proposal, title: "Winter is coming", question: "Do you speak it?", cached_votes_up: Proposal.votes_needed_for_success + 100), create(:proposal, title: "Fire and blood", question: "You talking to me?", cached_votes_up: Proposal.votes_needed_for_success + 1)] end