Merge branch 'master' into polls

This commit is contained in:
Juanjo Bazán
2017-01-19 16:51:19 +01:00
254 changed files with 10658 additions and 871 deletions

View File

@@ -10,5 +10,8 @@ before_script:
script:
- "bundle exec rake knapsack:rspec"
env:
- CI_NODE_TOTAL=2 CI_NODE_INDEX=0
- CI_NODE_TOTAL=2 CI_NODE_INDEX=1
global:
- CI_NODE_TOTAL=2
matrix:
- CI_NODE_INDEX=0
- CI_NODE_INDEX=1

View File

@@ -17,6 +17,7 @@ Para adaptarlo puedes hacerlo a través de los directorios que están en custom
Aparte de estos directorios también cuentas con ciertos ficheros para:
* `app/assets/stylesheets/custom.css`
* `app/assets/stylesheets/_custom_settings.css`
* `app/assets/javascripts/custom.js`
* `Gemfile_custom`
* `config/application.custom.rb`
@@ -67,6 +68,11 @@ Si quieres cambiar algun selector CSS (de las hojas de estilo) puedes hacerlo en
background: red;
}
```
Si quieres cambiar alguna variable de foundation puedes hacerlo en el fichero `app/assets/stylesheets/_custom_settings.scss`. Por ejemplo para cambiar el color general de la aplicación puedes hacerlo agregando:
```css
$brand: #446336;
```
Usamos un preprocesador de CSS, [SASS, con la sintaxis SCSS](http://sass-lang.com/guide).

16
Gemfile
View File

@@ -14,13 +14,13 @@ gem 'coffee-rails', '~> 4.2.1'
# gem 'therubyracer', platforms: :ruby
# Use jquery as the JavaScript library
gem 'jquery-rails', '~> 4.2.1'
gem 'jquery-rails', '~> 4.2.2'
gem 'jquery-ui-rails'
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
gem 'turbolinks'
# Fix sprockets on the
gem 'sprockets', '~> 3.7.0'
gem 'sprockets', '~> 3.7.1'
gem 'devise', '~> 3.5.7'
gem 'devise_security_extension'
@@ -38,26 +38,26 @@ gem 'responders', '~> 2.3.0'
gem 'foundation-rails', '~> 6.2.4.0'
gem 'foundation_rails_helper', '~> 2.0.0'
gem 'acts_as_votable'
gem 'ckeditor', '~> 4.2.0'
gem 'invisible_captcha', '~> 0.9.1'
gem 'ckeditor', '~> 4.2.2'
gem 'invisible_captcha', '~> 0.9.2'
gem 'cancancan'
gem 'social-share-button'
gem 'initialjs-rails', '0.2.0.4'
gem 'unicorn', '~> 5.1.0'
gem 'unicorn', '~> 5.2.0'
gem 'paranoia', '~> 2.2.0'
gem 'rinku', '~> 2.0.2', require: 'rails_rinku'
gem 'savon'
gem 'dalli'
gem 'rollbar', '~> 2.13.3'
gem 'rollbar', '~> 2.14.0'
gem 'delayed_job_active_record', '~> 4.1.0'
gem 'daemons'
gem 'devise-async'
gem 'newrelic_rpm', '~> 3.17.1.326'
gem 'newrelic_rpm', '~> 3.17.2.327'
gem 'whenever', require: false
gem 'pg_search'
gem 'sitemap_generator'
gem 'ahoy_matey', '~> 1.4.2'
gem 'ahoy_matey', '~> 1.5.3'
gem 'groupdate', '~> 3.1.0' # group temporary data
gem 'tolk', '~> 2.0.0' # Web interface for translations

View File

@@ -40,11 +40,11 @@ GEM
activerecord (>= 3.2, < 5)
acts_as_votable (0.10.0)
addressable (2.4.0)
ahoy_matey (1.4.2)
ahoy_matey (1.5.3)
addressable
browser (~> 2.0)
geocoder
rack-attack
rack-attack (< 6)
railties
referer-parser (>= 0.3.0)
request_store
@@ -95,7 +95,7 @@ GEM
rack-test (>= 0.5.4)
xpath (~> 2.0)
chronic (0.10.2)
ckeditor (4.2.0)
ckeditor (4.2.2)
cocaine
orm_adapter (~> 0.5.0)
climate_control (0.0.3)
@@ -110,7 +110,7 @@ GEM
coffee-script-source
execjs
coffee-script-source (1.10.0)
concurrent-ruby (1.0.2)
concurrent-ruby (1.0.4)
coveralls (0.8.17)
json (>= 1.8, < 3)
simplecov (~> 0.12.0)
@@ -173,7 +173,7 @@ GEM
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
geocoder (1.4.0)
geocoder (1.4.1)
globalid (0.3.7)
activesupport (>= 4.1.0)
groupdate (3.1.1)
@@ -198,9 +198,9 @@ GEM
terminal-table (>= 1.5.1)
initialjs-rails (0.2.0.4)
railties (>= 3.1, < 6.0)
invisible_captcha (0.9.1)
rails
jquery-rails (4.2.1)
invisible_captcha (0.9.2)
rails (>= 3.2.0)
jquery-rails (4.2.2)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
@@ -211,7 +211,7 @@ GEM
kaminari (0.17.0)
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.10.0)
kgio (2.11.0)
knapsack (1.13.1)
rake
timecop (>= 0.1.0)
@@ -238,7 +238,7 @@ GEM
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (3.2.0)
newrelic_rpm (3.17.1.326)
newrelic_rpm (3.17.2.327)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
nori (2.6.0)
@@ -325,7 +325,7 @@ GEM
responders (2.3.0)
railties (>= 4.2.0, < 5.1)
rinku (2.0.2)
rollbar (2.13.3)
rollbar (2.14.0)
multi_json
rspec-core (3.5.4)
rspec-support (~> 3.5.0)
@@ -385,7 +385,7 @@ GEM
spring (1.7.2)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (3.7.0)
sprockets (3.7.1)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-es6 (0.9.2)
@@ -424,7 +424,7 @@ GEM
uglifier (3.0.4)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.1.1)
unicorn (5.1.0)
unicorn (5.2.0)
kgio (~> 2.6)
raindrops (~> 0.7)
uniform_notifier (1.10.0)
@@ -453,7 +453,7 @@ PLATFORMS
DEPENDENCIES
acts-as-taggable-on
acts_as_votable
ahoy_matey (~> 1.4.2)
ahoy_matey (~> 1.5.3)
ancestry (~> 2.2.2)
browser
bullet
@@ -464,7 +464,7 @@ DEPENDENCIES
capistrano-rails (= 1.1.8)
capistrano3-delayed-job (~> 1.0)
capybara
ckeditor (~> 4.2.0)
ckeditor (~> 4.2.2)
coffee-rails (~> 4.2.1)
coveralls
daemons
@@ -483,14 +483,14 @@ DEPENDENCIES
groupdate (~> 3.1.0)
i18n-tasks
initialjs-rails (= 0.2.0.4)
invisible_captcha (~> 0.9.1)
jquery-rails (~> 4.2.1)
invisible_captcha (~> 0.9.2)
jquery-rails (~> 4.2.2)
jquery-ui-rails
kaminari
knapsack
launchy
letter_opener_web (~> 1.3.0)
newrelic_rpm (~> 3.17.1.326)
newrelic_rpm (~> 3.17.2.327)
omniauth
omniauth-facebook (~> 3.0.0)
omniauth-google-oauth2 (~> 0.4.0)
@@ -504,7 +504,7 @@ DEPENDENCIES
redcarpet
responders (~> 2.3.0)
rinku (~> 2.0.2)
rollbar (~> 2.13.3)
rollbar (~> 2.14.0)
rspec-rails (~> 3.5)
rubocop (~> 0.45.0)
rvm1-capistrano3
@@ -514,14 +514,11 @@ DEPENDENCIES
social-share-button
spring
spring-commands-rspec
sprockets (~> 3.7.0)
sprockets (~> 3.7.1)
tolk (~> 2.0.0)
turbolinks
turnout (~> 2.4.0)
uglifier (>= 3.0.4)
unicorn (~> 5.1.0)
unicorn (~> 5.2.0)
web-console (= 3.3.0)
whenever
BUNDLED WITH
1.13.6

View File

@@ -0,0 +1,12 @@
App.AllowParticipation =
initialize: ->
$(document).on {
'mouseenter focus': ->
$(this).find(".js-participation-not-allowed").show();
$(this).find(".js-participation-allowed").hide();
mouseleave: ->
$(this).find(".js-participation-not-allowed").hide();
$(this).find(".js-participation-allowed").show();
}, ".js-participation"
false

View File

@@ -36,14 +36,17 @@
//= require tags
//= require users
//= require votes
//= require allow_participation
//= require annotatable
//= require advanced_search
//= require registration_form
//= require suggest
//= require forms
//= require tracks
//= require valuation_budget_investment_form
//= require valuation_spending_proposal_form
//= require embed_video
//= require fixed_bar
//= require banners
//= require social_share
//= require custom
@@ -52,6 +55,7 @@ var initialize_modules = function() {
App.Comments.initialize();
App.Users.initialize();
App.Votes.initialize();
App.AllowParticipation.initialize();
App.Tags.initialize();
App.Dropdown.initialize();
App.LocationChanger.initialize();
@@ -64,8 +68,10 @@ var initialize_modules = function() {
App.Suggest.initialize();
App.Forms.initialize();
App.Tracks.initialize();
App.ValuationBudgetInvestmentForm.initialize();
App.ValuationSpendingProposalForm.initialize();
App.EmbedVideo.initialize();
App.FixedBar.initialize();
App.Banners.initialize();
App.SocialShare.initialize();
};

View File

@@ -0,0 +1,13 @@
App.FixedBar =
initialize: ->
$('[data-fixed-bar]').each ->
$this = $(this)
fixedBarTopPosition = $this.offset().top
$(window).on 'scroll', ->
if $(window).scrollTop() > fixedBarTopPosition
$this.addClass('is-fixed')
$("#check-ballot").css({ 'display': "inline-block" });
else
$this.removeClass('is-fixed')
$("#check-ballot").hide()

View File

@@ -0,0 +1,32 @@
App.ValuationBudgetInvestmentForm =
showFeasibleFields: ->
$('#valuation_budget_investment_edit_form #unfeasible_fields').hide('down')
$('#valuation_budget_investment_edit_form #feasible_fields').show()
showNotFeasibleFields: ->
$('#valuation_budget_investment_edit_form #feasible_fields').hide('down')
$('#valuation_budget_investment_edit_form #unfeasible_fields').show()
showAllFields: ->
$('#valuation_budget_investment_edit_form #feasible_fields').show('down')
$('#valuation_budget_investment_edit_form #unfeasible_fields').show('down')
showFeasibilityFields: ->
feasibility = $("#valuation_budget_investment_edit_form input[type=radio][name='budget_investment[feasibility]']:checked").val()
if feasibility == 'feasible'
App.ValuationBudgetInvestmentForm.showFeasibleFields()
else if feasibility == 'unfeasible'
App.ValuationBudgetInvestmentForm.showNotFeasibleFields()
showFeasibilityFieldsOnChange: ->
$("#valuation_budget_investment_edit_form input[type=radio][name='budget_investment[feasibility]']").change ->
App.ValuationBudgetInvestmentForm.showAllFields()
App.ValuationBudgetInvestmentForm.showFeasibilityFields()
initialize: ->
App.ValuationBudgetInvestmentForm.showFeasibilityFields()
App.ValuationBudgetInvestmentForm.showFeasibilityFieldsOnChange()
false

View File

@@ -0,0 +1,5 @@
// Overrides and adds customized foundation settings in this file
// Read more on documentation:
// * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#css
// * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#css
//

View File

@@ -76,7 +76,7 @@ $check: #46DB91;
$proposals: #FFA42D;
$proposals-dark: #794500;
$budget: #454372;
$budget: #7E328A;
$budget-hover: #7571BF;
$highlight: #E7F2FC;

View File

@@ -59,6 +59,10 @@ body.admin {
select {
height: $line-height*2;
}
.input-group input[type="text"] {
border-radius: 0;
margin-bottom: 0 !important;
}
}
@@ -74,6 +78,10 @@ body.admin {
&.text-right {
text-align: right;
}
&.with-button {
line-height: $line-height*2;
}
}
tr {
@@ -456,7 +464,7 @@ body.admin {
}
}
.admin-content .select-geozone {
.admin-content .select-geozone, .admin-content .select-heading {
a {
display: block;

View File

@@ -1,6 +1,7 @@
@charset 'utf-8';
@import 'settings';
@import 'custom_settings';
@import 'foundation';
@include foundation-global-styles;

View File

@@ -184,6 +184,9 @@
.icon-arrow-top:before {
content: "\57";
}
.icon-help-1:before {
content: "\58";
}
.icon-checkmark-circle:before {
content: "\59";
}

View File

@@ -629,12 +629,28 @@ h2.sidebar-title {
// --------------
.auth-page {
.wrapper {
margin: 0 auto (-$line-height)*14;
}
}
.auth-image {
background: $brand image-url("auth_bg.jpg");
background-repeat: no-repeat;
background-size: cover;
h1:not(.logo) {
@include logo;
@include breakpoint(medium) {
min-height: $line-height*42;
}
h1 {
margin-top: $line-height;
img {
height: rem-calc(80);
width: rem-calc(80);
}
a {
color: white;
@@ -650,15 +666,27 @@ h2.sidebar-title {
}
}
.auth {
.auth-form {
@include breakpoint(medium) {
padding-top: $line-height*4;
}
p, a, .checkbox {
font-size: $small-font-size;
}
}
.panel {
.auth-divider {
border-bottom: 1px solid $border;
height: rem-calc(14);
margin: $line-height 0;
text-align: center;
span {
background: white;
border: 0;
font-weight: bold;
padding: 0 $line-height/2;
}
}
@@ -834,6 +862,11 @@ form {
.callout {
font-size: $small-font-size;
a {
font-weight: bold;
text-decoration: underline;
}
&.success, &.notice {
background-color: $success-bg;
border-color: $success-border;
@@ -850,12 +883,6 @@ form {
background-color: $warning-bg;
border-color: $warning-border;
color: $color-warning;
a {
color: $color-warning;
font-weight: bold;
text-decoration: underline;
}
}
&.alert, &.error {

View File

@@ -5,8 +5,9 @@
// 03. Show participation
// 04. List participation
// 05. Featured
// 06. Proposals successful
// 07. Polls
// 06. Budget
// 07. Proposals successful
// 08. Polls
//
// 01. Votes and supports
@@ -239,6 +240,7 @@
.debate-form,
.proposal-form,
.budget-investment-form,
.spending-proposal-form {
.icon-debates, .icon-proposals, .icon-budget {
@@ -295,7 +297,8 @@
.debate-show,
.proposal-show,
.investment-project-show {
.investment-project-show,
.budget-investment-show {
p {
word-wrap: break-word;
@@ -322,14 +325,14 @@
margin-bottom: 0;
}
.debate-info, .proposal-info, .investment-project-info {
.debate-info, .proposal-info, .investment-project-info, .budget-investment-show {
clear: both;
color: $text-medium;
font-size: $small-font-size;
margin-bottom: $line-height/2;
position: relative;
span {
span:not(.label) {
line-height: rem-calc(32); // Same as avatar height
}
@@ -456,11 +459,11 @@
color: $border;
}
.investment-project-show p {
.investment-project-show p, .budget-investment-show p {
word-break: break-word;
}
.proposal-show, .investment-project-show {
.proposal-show, .investment-project-show, .budget-investment-show {
.supports {
padding: $line-height/2 0 0;
@@ -474,21 +477,21 @@
// 04. List participation
// ----------------------
.debates-list, .proposals-list, .investment-projects-list {
.debates-list, .proposals-list, .investment-projects-list, .budget-investments-list {
@include breakpoint(medium) {
margin-bottom: rem-calc(48);
}
}
.investment-projects-list {
.investment-projects-list, .budget-investments-list {
@include breakpoint(medium) {
min-height: $line-height*15;
}
}
.debate, .proposal, .investment-project {
.debate, .proposal, .investment-project, .budget-investment {
margin: $line-height/4 0;
.panel {
@@ -506,7 +509,7 @@
padding-bottom: rem-calc(12);
}
.label-debate, .label-proposal, .label-investment-project {
.label-debate, .label-proposal, .label-investment-project, .label-budget-investment {
background: none;
clear: both;
display: block;
@@ -531,6 +534,10 @@
color: $budget;
}
.label-budget-investment {
color: $budget;
}
h3 {
font-weight: bold;
margin: 0;
@@ -540,7 +547,7 @@
}
}
.debate-content, .proposal-content, .investment-project-content {
.debate-content, .proposal-content, .investment-project-content, .budget-investment-content {
margin: 0;
min-height: rem-calc(180);
position: relative;
@@ -570,7 +577,7 @@
font-size: $small-font-size;
}
.debate-info, .proposal-info, .investment-project-info {
.debate-info, .proposal-info, .investment-project-info, .budget-investment-info {
color: $text-medium;
font-size: $small-font-size;
margin: rem-calc(6) 0 0;
@@ -585,7 +592,7 @@
}
}
.debate-description, .proposal-description, .investment-project-description {
.debate-description, .proposal-description, .investment-project-description, .budget-investment-description {
color: $text;
font-size: rem-calc(13);
height: rem-calc(72);
@@ -670,12 +677,14 @@
}
}
.investment-project, .investment-project-show {
.investment-project, .investment-project-show,
.budget-investment, .budget-investment-show {
.supports {
@include supports;
.investment-project-amount {
.investment-project-amount,
.budget-investment-amount {
color: $budget;
font-size: rem-calc(20);
font-weight: bold;
@@ -704,6 +713,7 @@
}
.remove .icon-check-circle {
color: $budget;
display: block;
font-size: rem-calc(70);
line-height: rem-calc(70);
@@ -711,6 +721,11 @@
}
}
.investment-project-show .supports,
.budget-investment-show .supports {
border: 0;
}
.proposals-summary {
.panel {
@@ -719,11 +734,38 @@
}
.investment-project .supports .total-supports.no-button,
.investment-project-show .supports .total-supports.no-button {
.investment-project-show .supports .total-supports.no-button,
.budget-investment .supports .total-supports.no-button,
.budget-investment-show .supports .total-supports.no-button {
display: block;
margin-top: $line-height*1.5;
}
.budget-investment-show {
.label-budget-investment {
background: none;
clear: both;
color: $budget;
display: block;
font-size: rem-calc(12);
font-weight: bold;
line-height: $line-height;
padding-bottom: 0;
padding-left: 0;
padding-top: 0;
text-transform: uppercase;
}
.icon-budget {
color: $budget;
font-size: $small-font-size;
line-height: $line-height;
margin-left: rem-calc(6);
top: 0;
}
}
// 05. Featured
// ------------
@@ -835,7 +877,333 @@
}
}
// 06. Proposals successful
// 06. Budget
// ----------
.expanded.budget {
background: $budget;
h1, h2, p, a.back, .icon-angle-left {
color: white;
}
.button {
background: white;
color: $budget;
}
.info {
background: #6A2A72;
p {
margin-bottom: 0;
text-transform: uppercase;
}
@include breakpoint(medium) {
border-top: rem-calc(6) solid #54225C;
}
}
}
.jumbo-budget {
background: $budget;
border-bottom: 1px solid $budget;
&.budget-heading {
min-height: $line-height*10;
}
h1 {
margin-bottom: 0;
}
h1, h2, .back, .icon-angle-left, p, a {
color: white;
}
.callout.warning {
font-size: $base-font-size;
a {
color: $color-warning;
}
}
&.welcome {
background: $budget image-url('spending_proposals_bg.jpg');
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
.spending-proposal-timeline {
padding-top: $line-height;
ul li {
margin-right: $line-height;
padding-top: $line-height/2;
.icon-calendar {
display: none;
}
}
}
}
a {
text-decoration: underline;
&.button {
background: white;
color: $brand;
margin-bottom: rem-calc(3);
text-decoration: none;
}
}
.social-share-button a {
color: white;
&.social-share-button-twitter:hover {
color: #40A2D1;
}
&.social-share-button-facebook:hover {
color: #354F88;
}
&.social-share-button-google_plus:hover {
color: #CE3E26;
}
}
}
.progress-votes {
position: relative;
.progress {
background: #212033;
clear: both;
}
.progress-meter {
background: #fdcb10;
border-radius: 0;
-webkit-transition: width 2s;
transition: width 2s;
}
.spent-amount-progress,
.spent-amount-meter {
background: none !important;
}
.spent-amount-text {
color: white;
font-size: $base-font-size;
font-weight: normal;
position: absolute;
right: 0;
text-align: right;
top: 16px;
width: 100%;
&:before {
color: #a5a1ff;
content: "\57";
font-family: 'icons';
font-size: $small-font-size;
position: absolute;
right: -6px;
top: -17px;
}
}
.total-amount {
color: white;
font-size: rem-calc(18);
font-weight: bold;
float: right;
}
.amount-available {
display: block;
text-align: right;
span {
font-size: rem-calc(24);
font-weight: bold;
}
}
}
.big-number {
color: $budget;
font-size: rem-calc(60);
line-height: rem-calc(120);
@include breakpoint(large) {
font-size: rem-calc(90);
line-height: rem-calc(240);
}
}
.ballot {
h2, h3 {
font-weight: normal;
span {
color: $budget;
font-weight: bold;
}
}
h3.subtitle {
border-bottom: 3px solid $budget;
span {
font-size: $base-font-size;
font-weight: normal;
}
}
.amount-spent {
background: $success-bg;
font-weight: normal;
padding: $line-height/2;
span {
font-size: rem-calc(24);
font-weight: bold;
}
}
}
ul.ballot-list {
list-style: none;
margin-left: 0;
li {
background: #f9f9f9;
line-height: $line-height;
margin-bottom: $line-height/4;
padding: $line-height/2;
position: relative;
a {
color: $text;
}
span {
color: #9f9f9f;
display: block;
font-style: italic;
}
.remove-investment-project {
display: block;
height: 0;
.icon-x {
color: #9f9f9f;
font-size: rem-calc(24);
line-height: $line-height/2;
position: absolute;
right: 6px;
text-decoration: none;
top: 6px;
@include breakpoint(medium) {
font-size: $base-font-size;
}
}
}
&:hover {
background: $budget;
color: white;
a, span {
color: white;
outline: 0;
text-decoration: none;
}
.remove-investment-project .icon-x {
color: white;
}
}
}
}
.select-district a {
display: inline-block;
margin: $line-height/4 0;
padding: $line-height/4;
}
.select-district .active a {
background: #f9f9f9;
border-radius: rem-calc(3);
color: $budget;
font-weight: bold;
&:after {
content: "\56";
font-family: "icons";
font-size: $small-font-size;
font-weight: normal;
line-height: $line-height;
padding-left: rem-calc(3);
vertical-align: baseline;
&:hover {
text-decoration: none;
}
}
}
.progress-bar-nav {
padding: $line-height 0;
position: relative;
z-index: 3;
@include breakpoint(medium) {
background-color: $budget;
-webkit-transition: height 0.3s;
-moz-transition: height 0.3s;
transition: height 0.3s;
h1 {
-webkit-transition: font-size 0.3s;
-moz-transition: font-size 0.3s;
transition: font-size 0.3s;
}
h2 {
margin-bottom: 0;
}
&.is-fixed {
height: auto;
left: 0;
padding: $line-height;
position: fixed;
top: 0;
width: 100%;
h1 {
font-size: rem-calc(24);
-webkit-transition: font-size 0.3s;
-moz-transition: font-size 0.3s;
transition: font-size 0.3s;
}
}
}
}
// 07. Proposals successful
// -------------------------
.dark-heading {
@@ -938,7 +1306,7 @@
}
}
// 07. Polls
// 08. Polls
// ----------------------
.dark-heading {

View File

@@ -0,0 +1,17 @@
class Admin::BudgetGroupsController < Admin::BaseController
include FeatureFlags
feature_flag :budgets
def create
@budget = Budget.find params[:budget_id]
@budget.groups.create(budget_group_params)
@groups = @budget.groups.includes(:headings)
end
private
def budget_group_params
params.require(:budget_group).permit(:name)
end
end

View File

@@ -0,0 +1,18 @@
class Admin::BudgetHeadingsController < Admin::BaseController
include FeatureFlags
feature_flag :budgets
def create
@budget = Budget.find params[:budget_id]
@budget_group = @budget.groups.find params[:budget_group_id]
@budget_group.headings.create(budget_heading_params)
@headings = @budget_group.headings
end
private
def budget_heading_params
params.require(:budget_heading).permit(:name, :price, :geozone_id)
end
end

View File

@@ -0,0 +1,86 @@
class Admin::BudgetInvestmentsController < Admin::BaseController
include FeatureFlags
feature_flag :budgets
has_filters(%w{valuation_open without_admin managed valuating valuation_finished
valuation_finished_feasible selected all},
only: [:index, :toggle_selection])
before_action :load_budget
before_action :load_investment, only: [:show, :edit, :update, :toggle_selection]
before_action :load_ballot, only: [:show, :index]
before_action :load_investments, only: [:index, :toggle_selection]
def index
end
def show
end
def edit
load_admins
load_valuators
load_tags
end
def update
set_valuation_tags
if @investment.update(budget_investment_params)
redirect_to admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)),
notice: t("flash.actions.update.budget_investment")
else
load_admins
load_valuators
load_tags
render :edit
end
end
def toggle_selection
@investment.toggle :selected
@investment.save
end
private
def load_investments
@investments = Budget::Investment.scoped_filter(params, @current_filter)
.order(cached_votes_up: :desc, created_at: :desc)
.page(params[:page])
end
def budget_investment_params
params.require(:budget_investment)
.permit(:title, :description, :external_url, :heading_id, :administrator_id, :valuation_tag_list, valuator_ids: [])
end
def load_budget
@budget = Budget.includes(:groups).find params[:budget_id]
end
def load_investment
@investment = Budget::Investment.where(budget_id: @budget.id).find params[:id]
end
def load_admins
@admins = Administrator.includes(:user).all
end
def load_valuators
@valuators = Valuator.includes(:user).all.order("description ASC").order("users.email ASC")
end
def load_tags
@tags = Budget::Investment.tags_on(:valuation).order(:name).uniq
end
def load_ballot
query = Budget::Ballot.where(user: current_user, budget: @budget)
@ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize
end
def set_valuation_tags
@investment.set_tag_list_on(:valuation, budget_investment_params[:valuation_tag_list])
params[:budget_investment] = params[:budget_investment].except(:valuation_tag_list)
end
end

View File

@@ -0,0 +1,48 @@
class Admin::BudgetsController < Admin::BaseController
include FeatureFlags
feature_flag :budgets
has_filters %w{current finished}, only: :index
load_and_authorize_resource
def index
@budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page])
end
def show
@budget = Budget.includes(groups: :headings).find(params[:id])
end
def new
end
def edit
end
def update
if @budget.update(budget_params)
redirect_to admin_budget_path(@budget), notice: t('admin.budgets.update.notice')
else
render :edit
end
end
def create
@budget = Budget.new(budget_params)
if @budget.save
redirect_to admin_budget_path(@budget), notice: t('admin.budgets.create.notice')
else
render :new
end
end
private
def budget_params
descriptions = Budget::PHASES.map{|p| "description_#{p}"}.map(&:to_sym)
valid_attributes = [:name, :phase, :currency_symbol] + descriptions
params.require(:budget).permit(*valid_attributes)
end
end

View File

@@ -0,0 +1,78 @@
module Budgets
module Ballot
class LinesController < ApplicationController
before_action :authenticate_user!
#before_action :ensure_final_voting_allowed
before_action :load_budget
before_action :load_ballot
before_action :load_tag_cloud
before_action :load_categories
before_action :load_investments
load_and_authorize_resource :budget
load_and_authorize_resource :ballot, class: "Budget::Ballot", through: :budget
load_and_authorize_resource :line, through: :ballot, find_by: :investment_id, class: "Budget::Ballot::Line"
def create
load_investment
load_heading
unless @ballot.add_investment(@investment)
head :bad_request
end
end
def destroy
@investment = @line.investment
load_heading
@line.destroy
load_investments
#@ballot.reset_geozone
end
private
def ensure_final_voting_allowed
return head(:forbidden) unless @budget.balloting?
end
def line_params
params.permit(:investment_id, :budget_id)
end
def load_budget
@budget = Budget.find(params[:budget_id])
end
def load_ballot
@ballot = Budget::Ballot.where(user: current_user, budget: @budget).first_or_create
end
def load_investment
@investment = Budget::Investment.find(params[:investment_id])
end
def load_investments
if params[:investments_ids].present?
@investment_ids = params[:investment_ids]
@investments = Budget::Investment.where(id: params[:investments_ids])
end
end
def load_heading
@heading = @investment.heading
end
def load_tag_cloud
@tag_cloud = TagCloud.new(Budget::Investment, params[:search])
end
def load_categories
@categories = ActsAsTaggableOn::Tag.where("kind = 'category'").order(:name)
end
end
end
end

View File

@@ -0,0 +1,20 @@
module Budgets
class BallotsController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource :budget
before_action :load_ballot
def show
authorize! :show, @ballot
render template: "budgets/ballot/show"
end
private
def load_ballot
query = Budget::Ballot.where(user: current_user, budget: @budget)
@ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize
end
end
end

View File

@@ -0,0 +1,10 @@
module Budgets
class GroupsController < ApplicationController
load_and_authorize_resource :budget
load_and_authorize_resource :group, class: "Budget::Group"
def show
end
end
end

View File

@@ -0,0 +1,111 @@
module Budgets
class InvestmentsController < ApplicationController
include FeatureFlags
include CommentableActions
include FlagActions
before_action :authenticate_user!, except: [:index, :show]
load_and_authorize_resource :budget
load_and_authorize_resource :investment, through: :budget, class: "Budget::Investment"
before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] }
before_action :load_ballot, only: [:index, :show]
before_action :load_heading, only: [:index, :show]
before_action :set_random_seed, only: :index
before_action :load_categories, only: [:index, :new, :create]
feature_flag :budgets
has_orders %w{most_voted newest oldest}, only: :show
has_orders ->(c) { c.instance_variable_get(:@budget).investments_orders }, only: :index
invisible_captcha only: [:create, :update], honeypot: :subtitle, scope: :budget_investment
respond_to :html, :js
def index
@investments = @investments.apply_filters_and_search(@budget, params).send("sort_by_#{@current_order}").page(params[:page]).per(10).for_render
@investment_ids = @investments.pluck(:id)
load_investment_votes(@investments)
@tag_cloud = tag_cloud
end
def new
end
def show
@commentable = @investment
@comment_tree = CommentTree.new(@commentable, params[:page], @current_order)
set_comment_flags(@comment_tree.comments)
load_investment_votes(@investment)
@investment_ids = [@investment.id]
end
def create
@investment.author = current_user
if @investment.save
Mailer.budget_investment_created(@investment).deliver_later
redirect_to budget_investment_path(@budget, @investment),
notice: t('flash.actions.create.budget_investment')
else
render :new
end
end
def destroy
investment.destroy
redirect_to user_path(current_user, filter: 'budget_investments'), notice: t('flash.actions.destroy.budget_investment')
end
def vote
@investment.register_selection(current_user)
load_investment_votes(@investment)
respond_to do |format|
format.html { redirect_to budget_investments_path(heading_id: @investment.heading.id) }
format.js
end
end
private
def load_investment_votes(investments)
@investment_votes = current_user ? current_user.budget_investment_votes(investments) : {}
end
def set_random_seed
if params[:order] == 'random' || params[:order].blank?
params[:random_seed] ||= rand(99)/100.0
Budget::Investment.connection.execute "select setseed(#{params[:random_seed]})"
else
params[:random_seed] = nil
end
end
def investment_params
params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id, :tag_list, :organization_name, :location, :terms_of_service)
end
def load_ballot
query = Budget::Ballot.where(user: current_user, budget: @budget)
@ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize
end
def load_heading
if params[:heading_id].present?
@heading = @budget.headings.find(params[:heading_id])
@assigned_heading = @ballot.try(:heading_for_group, @heading.try(:group))
end
end
def load_categories
@categories = ActsAsTaggableOn::Tag.where("kind = 'category'").order(:name)
end
def tag_cloud
TagCloud.new(Budget::Investment, params[:search])
end
end
end

View File

@@ -0,0 +1,15 @@
class BudgetsController < ApplicationController
include FeatureFlags
feature_flag :budgets
load_and_authorize_resource
respond_to :html, :js
def show
end
def index
@budgets = @budgets.order(:created_at)
end
end

View File

@@ -3,8 +3,9 @@ module HasOrders
class_methods do
def has_orders(valid_orders, *args)
before_action(*args) do
@valid_orders = valid_orders
before_action(*args) do |c|
@valid_orders = valid_orders.respond_to?(:call) ? valid_orders.call(c) : valid_orders.dup
@valid_orders.delete('relevance') if params[:search].blank?
@current_order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first
end
end

View File

@@ -0,0 +1,70 @@
class Management::Budgets::InvestmentsController < Management::BaseController
load_resource :budget
load_resource :investment, through: :budget, class: 'Budget::Investment'
before_action :only_verified_users, except: :print
before_action :load_heading, only: [:index, :show, :print]
def index
@investments = @investments.apply_filters_and_search(@budget, params).page(params[:page])
load_investment_votes(@investments)
end
def new
load_categories
end
def create
@investment.terms_of_service = "1"
@investment.author = managed_user
if @investment.save
notice= t('flash.actions.create.notice', resource_name: Budget::Investment.model_name.human, count: 1)
redirect_to management_budget_investment_path(@budget, @investment), notice: notice
else
render :new
end
end
def show
load_investment_votes(@investment)
end
def vote
@investment.register_selection(managed_user)
load_investment_votes(@investment)
respond_to do |format|
format.html { redirect_to management_budget_investments_path(heading_id: @investment.heading.id) }
format.js
end
end
def print
@investments = @investments.apply_filters_and_search(@budget, params).order(cached_votes_up: :desc).for_render.limit(15)
load_investment_votes(@investments)
end
private
def load_investment_votes(investments)
@investment_votes = managed_user ? managed_user.budget_investment_votes(investments) : {}
end
def investment_params
params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id)
end
def only_verified_users
check_verified_user t("management.budget_investments.alert.unverified_user")
end
def load_heading
@heading = @budget.headings.find(params[:heading_id]) if params[:heading_id].present?
end
def load_categories
@categories = ActsAsTaggableOn::Tag.where("kind = 'category'").order(:name)
end
end

View File

@@ -0,0 +1,26 @@
class Management::BudgetsController < Management::BaseController
include FeatureFlags
include HasFilters
feature_flag :budgets
before_action :only_verified_users, except: :print_investments
def create_investments
@budgets = Budget.accepting.order(created_at: :desc).page(params[:page])
end
def support_investments
@budgets = Budget.selecting.order(created_at: :desc).page(params[:page])
end
def print_investments
@budgets = Budget.current.order(created_at: :desc).page(params[:page])
end
private
def only_verified_users
check_verified_user t("management.budget_investments.alert.unverified_user")
end
end

View File

@@ -1,8 +1,7 @@
class UsersController < ApplicationController
has_filters %w{proposals debates comments spending_proposals}, only: :show
has_filters %w{proposals debates budget_investments comments}, only: :show
load_and_authorize_resource
helper_method :authorized_for_filter?
helper_method :author?
helper_method :author_or_admin?
@@ -14,9 +13,9 @@ class UsersController < ApplicationController
def set_activity_counts
@activity_counts = HashWithIndifferentAccess.new(
proposals: Proposal.where(author_id: @user.id).count,
debates: Debate.where(author_id: @user.id).count,
comments: Comment.not_as_admin_or_moderator.where(user_id: @user.id).count,
spending_proposals: SpendingProposal.where(author_id: @user.id).count)
debates: (Setting['feature.debates'] ? Debate.where(author_id: @user.id).count : 0),
budget_investments: (Setting['feature.budgets'] ? Budget::Investment.where(author_id: @user.id).count : 0),
comments: only_active_commentables.count)
end
def load_filtered_activity
@@ -24,8 +23,8 @@ class UsersController < ApplicationController
case params[:filter]
when "proposals" then load_proposals
when "debates" then load_debates
when "budget_investments" then load_budget_investments
when "comments" then load_comments
when "spending_proposals" then load_spending_proposals if author_or_admin?
else load_available_activity
end
end
@@ -34,15 +33,15 @@ class UsersController < ApplicationController
if @activity_counts[:proposals] > 0
load_proposals
@current_filter = "proposals"
elsif @activity_counts[:debates] > 0
elsif @activity_counts[:debates] > 0
load_debates
@current_filter = "debates"
elsif @activity_counts[:budget_investments] > 0
load_budget_investments
@current_filter = "budget_investments"
elsif @activity_counts[:comments] > 0
load_comments
@current_filter = "comments"
elsif @activity_counts[:spending_proposals] > 0 && author_or_admin?
load_spending_proposals
@current_filter = "spending_proposals"
end
end
@@ -55,11 +54,11 @@ class UsersController < ApplicationController
end
def load_comments
@comments = Comment.not_as_admin_or_moderator.where(user_id: @user.id).includes(:commentable).order(created_at: :desc).page(params[:page])
@comments = only_active_commentables.includes(:commentable).order(created_at: :desc).page(params[:page])
end
def load_spending_proposals
@spending_proposals = SpendingProposal.where(author_id: @user.id).order(created_at: :desc).page(params[:page])
def load_budget_investments
@budget_investments = Budget::Investment.where(author_id: @user.id).order(created_at: :desc).page(params[:page])
end
def valid_access?
@@ -78,7 +77,19 @@ class UsersController < ApplicationController
@authorized_current_user ||= current_user && (current_user == @user || current_user.moderator? || current_user.administrator?)
end
def authorized_for_filter?(filter)
filter == "spending_proposals" ? author_or_admin? : true
def all_user_comments
Comment.not_as_admin_or_moderator.where(user_id: @user.id)
end
def only_active_commentables
disabled_commentables = []
disabled_commentables << "Debate" unless Setting['feature.debates']
disabled_commentables << "Budget::Investment" unless Setting['feature.budgets']
if disabled_commentables.present?
all_user_comments.where("commentable_type NOT IN (?)", disabled_commentables)
else
all_user_comments
end
end
end

View File

@@ -0,0 +1,84 @@
class Valuation::BudgetInvestmentsController < Valuation::BaseController
include FeatureFlags
feature_flag :budgets
before_action :restrict_access_to_assigned_items, only: [:show, :edit, :valuate]
before_action :load_budget
before_action :load_investment, only: [:show, :edit, :valuate]
has_filters %w{valuating valuation_finished}, only: :index
load_and_authorize_resource :investment, class: "Budget::Investment"
def index
@heading_filters = heading_filters
if current_user.valuator? && @budget.present?
@investments = @budget.investments.scoped_filter(params_for_current_valuator, @current_filter).order(cached_votes_up: :desc).page(params[:page])
else
@investments = Budget::Investment.none.page(params[:page])
end
end
def valuate
if valid_price_params? && @investment.update(valuation_params)
if @investment.unfeasible_email_pending?
@investment.send_unfeasible_email
end
redirect_to valuation_budget_budget_investment_path(@budget, @investment), notice: t('valuation.budget_investments.notice.valuate')
else
render action: :edit
end
end
private
def load_budget
@budget = Budget.find(params[:budget_id])
end
def load_investment
@investment = @budget.investments.find params[:id]
end
def heading_filters
investments = @budget.investments.by_valuator(current_user.valuator.try(:id)).valuation_open.select(:heading_id).all.to_a
[ { name: t('valuation.budget_investments.index.headings_filter_all'),
id: nil,
pending_count: investments.size
}
] + Budget::Heading.where(id: investments.map(&:heading_id).uniq).order(name: :asc).collect do |h|
{ name: h.name,
id: h.id,
pending_count: investments.count{|x| x.heading_id == h.id}
}
end
end
def params_for_current_valuator
Budget::Investment.filter_params(params).merge({valuator_id: current_user.valuator.id, budget_id: @budget.id})
end
def valuation_params
params.require(:budget_investment).permit(:price, :price_first_year, :price_explanation, :feasibility, :unfeasibility_explanation, :duration, :valuation_finished, :internal_comments)
end
def restrict_access_to_assigned_items
raise ActionController::RoutingError.new('Not Found') unless current_user.administrator? || Budget::ValuatorAssignment.exists?(investment_id: params[:id], valuator_id: current_user.valuator.id)
end
def valid_price_params?
if /\D/.match params[:budget_investment][:price]
@investment.errors.add(:price, I18n.t('budgets.investments.wrong_price_format'))
end
if /\D/.match params[:budget_investment][:price_first_year]
@investment.errors.add(:price_first_year, I18n.t('budgets.investments.wrong_price_format'))
end
@investment.errors.empty?
end
end

View File

@@ -0,0 +1,17 @@
class Valuation::BudgetsController < Valuation::BaseController
include FeatureFlags
feature_flag :budgets
load_and_authorize_resource
def index
@budgets = @budgets.current.order(created_at: :desc).page(params[:page])
@investments_with_valuation_open = {}
@budgets.each do |b|
@investments_with_valuation_open[b.id] = b.investments
.by_valuator(current_user.valuator.try(:id))
.valuation_open
.count
end
end
end

View File

@@ -0,0 +1,7 @@
module BallotsHelper
def progress_bar_width(amount_available, amount_spent)
(amount_spent/amount_available.to_f * 100).to_s + "%"
end
end

View File

@@ -0,0 +1,9 @@
module BudgetHeadingsHelper
def budget_heading_select_options(budget)
budget.headings.order_by_group_name.map do |heading|
[heading.name_scoped_by_group, heading.id]
end
end
end

View File

@@ -0,0 +1,41 @@
module BudgetsHelper
def budget_phases_select_options
Budget::PHASES.map { |ph| [ t("budgets.phase.#{ph}"), ph ] }
end
def budget_currency_symbol_select_options
Budget::CURRENCY_SYMBOLS.map { |cs| [ cs, cs ] }
end
def namespaced_budget_investment_path(investment, options={})
case namespace
when "management::budgets"
management_budget_investment_path(investment.budget, investment, options)
else
budget_investment_path(investment.budget, investment, options)
end
end
def namespaced_budget_investment_vote_path(investment, options={})
case namespace
when "management::budgets"
vote_management_budget_investment_path(investment.budget, investment, options)
else
vote_budget_investment_path(investment.budget, investment, options)
end
end
def display_budget_countdown?(budget)
budget.balloting?
end
def css_for_ballot_heading(heading)
return '' unless current_ballot.present?
current_ballot.has_lines_in_heading?(heading) ? 'active' : ''
end
def current_ballot
Budget::Ballot.where(user: current_user, budget: @budget).first
end
end

View File

@@ -20,6 +20,14 @@ module CommentsHelper
end
end
def commentable_path(comment)
if comment.commentable_type == "Budget::Investment"
budget_investment_path(comment.commentable.budget_id, comment.commentable)
else
comment.commentable
end
end
def user_level_class(comment)
if comment.as_administrator?
"is-admin"

View File

@@ -8,4 +8,9 @@ module GeozonesHelper
Geozone.all.order(name: :asc).collect { |g| [ g.name, g.id ] }
end
def geozone_name_from_id(g_id)
@all_geozones ||= Geozone.all.collect{ |g| [ g.id, g.name ] }.to_h
@all_geozones[g_id] || t("geozones.none")
end
end

View File

@@ -3,6 +3,7 @@ module MailerHelper
def commentable_url(commentable)
return debate_url(commentable) if commentable.is_a?(Debate)
return proposal_url(commentable) if commentable.is_a?(Proposal)
return budget_investment_url(commentable.budget_id, commentable) if commentable.is_a?(Budget::Investment)
end
end

View File

@@ -1,7 +0,0 @@
module OrdersHelper
def valid_orders
@valid_orders.reject { |order| order =='relevance' && params[:search].blank? }
end
end

View File

@@ -1,13 +1,8 @@
module SearchHelper
def official_level_search_options
options_for_select([
[t("shared.advanced_search.author_type_1"), 1],
[t("shared.advanced_search.author_type_2"), 2],
[t("shared.advanced_search.author_type_3"), 3],
[t("shared.advanced_search.author_type_4"), 4],
[t("shared.advanced_search.author_type_5"), 5]],
params[:advanced_search].try(:[], :official_level))
options_for_select((1..5).map{ |i| [setting["official_level_#{i}_name"], i] },
params[:advanced_search].try(:[], :official_level))
end
def date_range_options
@@ -28,4 +23,4 @@ module SearchHelper
params[:advanced_search].try(:[], :date_max).present?
end
end
end

View File

@@ -1,11 +1,27 @@
module TagsHelper
def taggable_path(taggable_type, tag_name)
def taggables_path(taggable_type, tag_name)
case taggable_type
when 'debate'
debates_path(search: tag_name)
when 'proposal'
proposals_path(search: tag_name)
when 'budget/investment'
budget_investments_path(@budget, search: tag_name)
else
'#'
end
end
def taggable_path(taggable)
taggable_type = taggable.class.name.underscore
case taggable_type
when 'debate'
debate_path(taggable)
when 'proposal'
proposal_path(taggable)
when 'budget/investment'
budget_investment_path(taggable.budget_id, taggable)
else
'#'
end

View File

@@ -30,6 +30,8 @@ module UsersHelper
t("users.show.deleted_proposal")
when "Debate"
t("users.show.deleted_debate")
when "Budget::Investment"
t("users.show.deleted_budget_investment")
else
t("users.show.deleted")
end

View File

@@ -11,14 +11,14 @@ module ValuationHelper
def assigned_valuators_info(valuators)
case valuators.size
when 0
t("valuation.spending_proposals.index.no_valuators_assigned")
t("valuation.budget_investments.index.no_valuators_assigned")
when 1
"<span title=\"#{t('valuation.spending_proposals.index.valuators_assigned', count: 1)}\">".html_safe +
"<span title=\"#{t('valuation.budget_investments.index.valuators_assigned', count: 1)}\">".html_safe +
valuators.first.name +
"</span>".html_safe
else
"<span title=\"".html_safe + valuators.map(&:name).join(', ') + "\">".html_safe +
t('valuation.spending_proposals.index.valuators_assigned', count: valuators.size) +
t('valuation.budget_investments.index.valuators_assigned', count: valuators.size) +
"</span>".html_safe
end
end

View File

@@ -74,6 +74,23 @@ class Mailer < ApplicationMailer
end
end
def budget_investment_created(investment)
@investment = investment
with_user(@investment.author) do
mail(to: @investment.author.email, subject: t('mailers.budget_investment_created.subject'))
end
end
def budget_investment_unfeasible(investment)
@investment = investment
@author = investment.author
with_user(@author) do
mail(to: @author.email, subject: t('mailers.budget_investment_unfeasible.subject', code: @investment.code))
end
end
private
def with_user(user, &block)

View File

@@ -4,7 +4,6 @@ module Abilities
def initialize(user)
self.merge Abilities::Moderation.new(user)
self.merge Abilities::Valuator.new(user)
can :restore, Comment
cannot :restore, Comment, hidden_at: nil
@@ -33,7 +32,7 @@ module Abilities
can :mark_featured, Debate
can :unmark_featured, Debate
can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question]
can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment]
can [:search, :create, :index, :destroy], ::Moderator
can [:search, :create, :index, :summary], ::Valuator
@@ -41,7 +40,14 @@ module Abilities
can :manage, Annotation
can [:read, :update, :destroy, :summary], SpendingProposal
can [:read, :update, :valuate, :destroy, :summary], SpendingProposal
can [:index, :read, :new, :create, :update, :destroy], Budget
can [:read, :create, :update, :destroy], Budget::Group
can [:read, :create, :update, :destroy], Budget::Heading
can [:hide, :update, :toggle_selection], Budget::Investment
can :valuate, Budget::Investment
can :create, Budget::ValuatorAssignment
can [:search, :edit, :update, :create, :index, :destroy], Banner

View File

@@ -18,8 +18,6 @@ module Abilities
end
can [:retire_form, :retire], Proposal, author_id: user.id
can :read, SpendingProposal
can :create, Comment
can :create, Debate
can :create, Proposal
@@ -46,6 +44,12 @@ module Abilities
can :vote_featured, Proposal
can :vote, SpendingProposal
can :create, SpendingProposal
can :create, Budget::Investment, budget: { phase: "accepting" }
can :vote, Budget::Investment, budget: { phase: "selecting" }
can [:show, :create], Budget::Ballot, budget: { phase: "balloting" }
can [:create, :destroy], Budget::Ballot::Line, budget: { phase: "balloting" }
can :create, DirectMessage
can :show, DirectMessage, sender_id: user.id
can(:answer, Poll, Poll.answerable_by(user)){ |poll| poll.answerable_by?(user) }

View File

@@ -8,10 +8,15 @@ module Abilities
can :read, Comment
can :read, Poll
can :read, Poll::Question
can [:read, :welcome], Budget
can :read, Budget::Investment
can :read, SpendingProposal
can :read, Legislation
can :read, User
can [:search, :read], Annotation
can [:read], Budget
can [:read], Budget::Group
can [:read, :print], Budget::Investment
can :new, DirectMessage
end
end

View File

@@ -5,7 +5,7 @@ module Abilities
def initialize(user)
self.merge Abilities::Moderation.new(user)
can :comment_as_moderator, [Debate, Comment, Proposal, Poll::Question]
can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment, Poll::Question]
end
end
end

View File

@@ -3,7 +3,10 @@ module Abilities
include CanCan::Ability
def initialize(user)
valuator = user.valuator
can [:read, :update, :valuate], SpendingProposal
can [:read, :update, :valuate], Budget::Investment, id: valuator.investment_ids
cannot [:update, :valuate], Budget::Investment, budget: { phase: 'finished' }
end
end
end
end

118
app/models/budget.rb Normal file
View File

@@ -0,0 +1,118 @@
class Budget < ActiveRecord::Base
include Measurable
PHASES = %w(accepting reviewing selecting valuating balloting reviewing_ballots finished).freeze
CURRENCY_SYMBOLS = %w(€ $ £ ¥).freeze
validates :name, presence: true
validates :phase, inclusion: { in: PHASES }
validates :currency_symbol, presence: true
has_many :investments, dependent: :destroy
has_many :ballots, dependent: :destroy
has_many :groups, dependent: :destroy
has_many :headings, through: :groups
before_validation :sanitize_descriptions
scope :on_hold, -> { where(phase: %w(reviewing valuating reviewing_ballots")) }
scope :accepting, -> { where(phase: "accepting") }
scope :reviewing, -> { where(phase: "reviewing") }
scope :selecting, -> { where(phase: "selecting") }
scope :valuating, -> { where(phase: "valuating") }
scope :balloting, -> { where(phase: "balloting") }
scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") }
scope :finished, -> { where(phase: "finished") }
scope :current, -> { where.not(phase: "finished") }
def description
self.send("description_#{self.phase}").try(:html_safe)
end
def self.description_max_length
2000
end
def accepting?
phase == "accepting"
end
def reviewing?
phase == "reviewing"
end
def selecting?
phase == "selecting"
end
def valuating?
phase == "valuating"
end
def balloting?
phase == "balloting"
end
def reviewing_ballots?
phase == "reviewing_ballots"
end
def finished?
phase == "finished"
end
def on_hold?
reviewing? || valuating? || reviewing_ballots?
end
def current?
!finished?
end
def heading_price(heading)
heading_ids.include?(heading.id) ? heading.price : -1
end
def translated_phase
I18n.t "budgets.phase.#{phase}"
end
def formatted_amount(amount)
ActionController::Base.helpers.number_to_currency(amount,
precision: 0,
locale: I18n.default_locale,
unit: currency_symbol)
end
def formatted_heading_price(heading)
formatted_amount(heading_price(heading))
end
def formatted_heading_amount_spent(heading)
formatted_amount(amount_spent(heading))
end
def investments_orders
case phase
when 'accepting', 'reviewing'
%w{random}
when 'balloting', 'reviewing_ballots'
%w{random price}
else
%w{random confidence_score}
end
end
private
def sanitize_descriptions
s = WYSIWYGSanitizer.new
PHASES.each do |phase|
sanitized = s.sanitize(self.send("description_#{phase}"))
self.send("description_#{phase}=", sanitized)
end
end
end

View File

@@ -0,0 +1,73 @@
class Budget
class Ballot < ActiveRecord::Base
belongs_to :user
belongs_to :budget
has_many :lines, dependent: :destroy
has_many :investments, through: :lines
has_many :groups, -> { uniq }, through: :lines
has_many :headings, -> { uniq }, through: :groups
def add_investment(investment)
lines.create!(investment: investment)
end
def total_amount_spent
investments.sum(:price).to_i
end
def amount_spent(heading)
investments.by_heading(heading.id).sum(:price).to_i
end
def formatted_amount_spent(heading)
budget.formatted_amount(amount_spent(heading))
end
def amount_available(heading)
budget.heading_price(heading) - amount_spent(heading)
end
def formatted_amount_available(heading)
budget.formatted_amount(amount_available(heading))
end
def has_lines_in_group?(group)
self.groups.include?(group)
end
def wrong_budget?(heading)
heading.budget_id != budget_id
end
def different_heading_assigned?(heading)
other_heading_ids = heading.group.heading_ids - [heading.id]
lines.where(heading_id: other_heading_ids).exists?
end
def valid_heading?(heading)
!wrong_budget?(heading) && !different_heading_assigned?(heading)
end
def has_lines_with_no_heading?
investments.no_heading.count > 0
end
def has_lines_with_heading?
self.heading_id.present?
end
def has_lines_in_heading?(heading)
investments.by_heading(heading.id).any?
end
def has_investment?(investment)
self.investment_ids.include?(investment.id)
end
def heading_for_group(group)
self.headings.where(group: group).first
end
end
end

View File

@@ -0,0 +1,39 @@
class Budget
class Ballot
class Line < ActiveRecord::Base
belongs_to :ballot
belongs_to :investment
belongs_to :heading
belongs_to :group
belongs_to :budget
validates :ballot_id, :investment_id, :heading_id, :group_id, :budget_id, presence: true
validate :check_selected
validate :check_sufficient_funds
validate :check_valid_heading
before_validation :set_denormalized_ids
def check_sufficient_funds
errors.add(:money, "insufficient funds") if ballot.amount_available(investment.heading) < investment.price.to_i
end
def check_valid_heading
errors.add(:heading, "This heading's budget is invalid, or a heading on the same group was already selected") unless ballot.valid_heading?(self.heading)
end
def check_selected
errors.add(:investment, "unselected investment") unless investment.selected?
end
private
def set_denormalized_ids
self.heading_id ||= self.investment.try(:heading_id)
self.group_id ||= self.investment.try(:group_id)
self.budget_id ||= self.investment.try(:budget_id)
end
end
end
end

View File

@@ -0,0 +1,10 @@
class Budget
class Group < ActiveRecord::Base
belongs_to :budget
has_many :headings, dependent: :destroy
validates :budget_id, presence: true
validates :name, presence: true
end
end

View File

@@ -0,0 +1,20 @@
class Budget
class Heading < ActiveRecord::Base
belongs_to :group
has_many :investments
validates :group_id, presence: true
validates :name, presence: true
validates :price, presence: true
delegate :budget, :budget_id, to: :group, allow_nil: true
scope :order_by_group_name, -> { includes(:group).order('budget_groups.name', 'budget_headings.name') }
def name_scoped_by_group
"#{group.name}: #{name}"
end
end
end

View File

@@ -0,0 +1,266 @@
class Budget
class Investment < ActiveRecord::Base
include Measurable
include Sanitizable
include Taggable
include Searchable
acts_as_votable
acts_as_paranoid column: :hidden_at
include ActsAsParanoidAliases
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
belongs_to :heading
belongs_to :group
belongs_to :budget
belongs_to :administrator
has_many :valuator_assignments, dependent: :destroy
has_many :valuators, through: :valuator_assignments
has_many :comments, as: :commentable
validates :title, presence: true
validates :author, presence: true
validates :description, presence: true
validates :heading_id, presence: true
validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required?
validates :title, length: { in: 4..Budget::Investment.title_max_length }
validates :description, length: { maximum: Budget::Investment.description_max_length }
validates :terms_of_service, acceptance: { allow_nil: false }, on: :create
scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc, id: :desc) }
scope :sort_by_price, -> { reorder(price: :desc, confidence_score: :desc, id: :desc) }
scope :sort_by_random, -> { reorder("RANDOM()") }
scope :valuation_open, -> { where(valuation_finished: false) }
scope :without_admin, -> { valuation_open.where(administrator_id: nil) }
scope :managed, -> { valuation_open.where(valuator_assignments_count: 0).where("administrator_id IS NOT ?", nil) }
scope :valuating, -> { valuation_open.where("valuator_assignments_count > 0 AND valuation_finished = ?", false) }
scope :valuation_finished, -> { where(valuation_finished: true) }
scope :valuation_finished_feasible, -> { where(valuation_finished: true, feasibility: "feasible") }
scope :feasible, -> { where(feasibility: "feasible") }
scope :unfeasible, -> { where(feasibility: "unfeasible") }
scope :not_unfeasible, -> { where.not(feasibility: "unfeasible") }
scope :undecided, -> { where(feasibility: "undecided") }
scope :with_supports, -> { where('cached_votes_up > 0') }
scope :selected, -> { where(selected: true) }
scope :last_week, -> { where("created_at >= ?", 7.days.ago)}
scope :by_group, -> (group_id) { where(group_id: group_id) }
scope :by_heading, -> (heading_id) { where(heading_id: heading_id) }
scope :by_admin, -> (admin_id) { where(administrator_id: admin_id) }
scope :by_tag, -> (tag_name) { tagged_with(tag_name) }
scope :by_valuator, -> (valuator_id) { where("budget_valuator_assignments.valuator_id = ?", valuator_id).joins(:valuator_assignments) }
scope :for_render, -> { includes(:heading) }
before_save :calculate_confidence_score
before_validation :set_responsible_name
before_validation :set_denormalized_ids
def self.filter_params(params)
params.select{|x,_| %w{heading_id group_id administrator_id tag_name valuator_id}.include? x.to_s }
end
def self.scoped_filter(params, current_filter)
results = Investment.where(budget_id: params[:budget_id])
results = results.where(group_id: params[:group_id]) if params[:group_id].present?
results = results.by_heading(params[:heading_id]) if params[:heading_id].present?
results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present?
results = results.by_tag(params[:tag_name]) if params[:tag_name].present?
results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present?
results = results.send(current_filter) if current_filter.present?
results.includes(:heading, :group, :budget, administrator: :user, valuators: :user)
end
def self.limit_results(results, budget, max_per_heading, max_for_no_heading)
return results if max_per_heading <= 0 && max_for_no_heading <= 0
ids = []
if max_per_heading > 0
budget.headings.pluck(:id).each do |hid|
ids += Investment.where(heading_id: hid).order(confidence_score: :desc).limit(max_per_heading).pluck(:id)
end
end
if max_for_no_heading > 0
ids += Investment.no_heading.order(confidence_score: :desc).limit(max_for_no_heading).pluck(:id)
end
conditions = ["investments.id IN (?)"]
values = [ids]
if max_per_heading == 0
conditions << "investments.heading_id IS NOT ?"
values << nil
elsif max_for_no_heading == 0
conditions << "investments.heading_id IS ?"
values << nil
end
results.where(conditions.join(' OR '), *values)
end
def searchable_values
{ title => 'A',
author.username => 'B',
heading.try(:name) => 'B',
tag_list.join(' ') => 'B',
description => 'C'
}
end
def self.search(terms)
self.pg_search(terms)
end
def self.by_heading(heading)
where(heading_id: heading == 'all' ? nil : heading.presence)
end
def undecided?
feasibility == "undecided"
end
def feasible?
feasibility == "feasible"
end
def unfeasible?
feasibility == "unfeasible"
end
def unfeasibility_explanation_required?
unfeasible? && valuation_finished?
end
def unfeasible_email_pending?
unfeasible_email_sent_at.blank? && unfeasible? && valuation_finished?
end
def total_votes
cached_votes_up + physical_votes
end
def code
"#{created_at.strftime('%Y')}-#{id}" + (administrator.present? ? "-A#{administrator.id}" : "")
end
def send_unfeasible_email
Mailer.budget_investment_unfeasible(self).deliver_later
update(unfeasible_email_sent_at: Time.current)
end
def reason_for_not_being_selectable_by(user)
return permission_problem(user) if permission_problem?(user)
return :different_heading_assigned unless valid_heading?(user)
return :no_selecting_allowed unless budget.selecting?
end
def reason_for_not_being_ballotable_by(user, ballot)
return permission_problem(user) if permission_problem?(user)
return :not_selected unless selected?
return :no_ballots_allowed unless budget.balloting?
return :different_heading_assigned unless ballot.valid_heading?(heading)
return :not_enough_money if ballot.present? && !enough_money?(ballot)
end
def permission_problem(user)
return :not_logged_in unless user
return :organization if user.organization?
return :not_verified unless user.can?(:vote, Budget::Investment)
return nil
end
def permission_problem?(user)
permission_problem(user).present?
end
def selectable_by?(user)
reason_for_not_being_selectable_by(user).blank?
end
def valid_heading?(user)
!different_heading_assigned?(user)
end
def different_heading_assigned?(user)
other_heading_ids = group.heading_ids - [heading.id]
voted_in?(other_heading_ids, user)
end
def voted_in?(heading_ids, user)
heading_ids.include? heading_voted_by_user?(user)
end
def heading_voted_by_user?(user)
user.votes.for_budget_investments(budget.investments.where(group: group)).
votables.map(&:heading_id).first
end
def ballotable_by?(user)
reason_for_not_being_ballotable_by(user).blank?
end
def enough_money?(ballot)
available_money = ballot.amount_available(self.heading)
price.to_i <= available_money
end
def register_selection(user)
vote_by(voter: user, vote: 'yes') if selectable_by?(user)
end
def calculate_confidence_score
self.confidence_score = ScoreCalculator.confidence_score(total_votes, total_votes)
end
def set_responsible_name
self.responsible_name = author.try(:document_number) if author.try(:document_number).present?
end
def should_show_aside?
(budget.selecting? && !unfeasible?) ||
(budget.balloting? && feasible?) ||
(budget.valuating? && feasible?)
end
def should_show_votes?
budget.selecting?
end
def should_show_vote_count?
budget.valuating?
end
def should_show_ballots?
budget.balloting?
end
def formatted_price
budget.formatted_amount(price)
end
def self.apply_filters_and_search(budget, params)
investments = all
if budget.balloting?
investments = investments.selected
else
investments = params[:unfeasible].present? ? investments.unfeasible : investments.not_unfeasible
end
investments = investments.by_heading(params[:heading_id]) if params[:heading_id].present?
investments = investments.search(params[:search]) if params[:search].present?
investments
end
private
def set_denormalized_ids
self.group_id ||= self.heading.try(:group_id)
self.budget_id ||= self.heading.try(:group).try(:budget_id)
end
end
end

View File

@@ -0,0 +1,6 @@
class Budget
class ValuatorAssignment < ActiveRecord::Base
belongs_to :valuator, counter_cache: :budget_investments_count
belongs_to :investment, counter_cache: true
end
end

View File

@@ -10,7 +10,8 @@ class Comment < ActiveRecord::Base
validates :body, presence: true
validates :user, presence: true
validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Poll::Question"]
validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment", "Poll::Question"]
validate :validate_body_length

View File

@@ -6,6 +6,10 @@ module Sanitizable
before_validation :sanitize_tag_list
end
def description
super.try :html_safe
end
protected
def sanitize_description

View File

@@ -59,10 +59,6 @@ class Debate < ActiveRecord::Base
"#{id}-#{title}".parameterize
end
def description
super.try :html_safe
end
def likes
cached_votes_up
end

View File

@@ -94,10 +94,6 @@ class Proposal < ActiveRecord::Base
summary
end
def description
super.try :html_safe
end
def total_votes
cached_votes_up
end

View File

@@ -32,7 +32,7 @@ class TagCloud
end
def table_name
resource_model.to_s.downcase.pluralize
resource_model.to_s.downcase.pluralize.gsub("::", "/")
end
end

View File

@@ -20,6 +20,7 @@ class User < ActiveRecord::Base
has_many :identities, dependent: :destroy
has_many :debates, -> { with_hidden }, foreign_key: :author_id
has_many :proposals, -> { with_hidden }, foreign_key: :author_id
has_many :budget_investments, -> { with_hidden }, foreign_key: :author_id, class_name: 'Budget::Investment'
has_many :comments, -> { with_hidden }
has_many :spending_proposals, foreign_key: :author_id
has_many :failed_census_calls
@@ -93,11 +94,20 @@ class User < ActiveRecord::Base
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def budget_investment_votes(budget_investments)
voted = votes.for_budget_investments(budget_investments)
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def comment_flags(comments)
comment_flags = flags.for_comments(comments)
comment_flags.each_with_object({}){ |f, h| h[f.flaggable_id] = true }
end
def voted_in_group?(group)
votes.for_budget_investments(Budget::Investment.where(group: group)).exists?
end
def administrator?
administrator.present?
end
@@ -195,6 +205,10 @@ class User < ActiveRecord::Base
@@username_max_length ||= self.columns.find { |c| c.name == 'username' }.limit || 60
end
def self.minimum_required_age
(Setting['min_age_to_participate'] || 16).to_i
end
def show_welcome_screen?
sign_in_count == 1 && unverified? && !organization && !administrator?
end

View File

@@ -4,6 +4,8 @@ class Valuator < ActiveRecord::Base
has_many :valuation_assignments, dependent: :destroy
has_many :spending_proposals, through: :valuation_assignments
has_many :valuator_assignments, dependent: :destroy, class_name: 'Budget::ValuatorAssignment'
has_many :investments, through: :valuator_assignments, class_name: 'Budget::Investment'
validates :user_id, presence: true, uniqueness: true

View File

@@ -23,7 +23,7 @@ class Verification::Management::Document
end
def valid_age?(response)
if under_sixteen?(response)
if under_age?(response)
errors.add(:age, true)
return false
else
@@ -31,8 +31,8 @@ class Verification::Management::Document
end
end
def under_sixteen?(response)
16.years.ago.beginning_of_day < response.date_of_birth.beginning_of_day
def under_age?(response)
User.minimum_required_age.years.ago.beginning_of_day < response.date_of_birth.beginning_of_day
end
def verified?

View File

@@ -36,7 +36,7 @@ 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 <= 16.years.ago
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
end
def document_number_uniqueness

View File

@@ -53,6 +53,7 @@
</li>
<% end %>
<% if feature?(:polls) %>
<li class="section-title">
<a href="#">
@@ -79,6 +80,15 @@
</li>
<% end %>
<% if feature?(:budgets) %>
<li class="section-title" <%= "class=active" if controller_name == "budgets" %>>
<%= link_to admin_budgets_path do %>
<span class="icon-budget"></span>
<strong><%= t("admin.menu.budgets") %></strong>
<% end %>
</li>
<% end %>
<li class="section-title">
<a href="#">
<span class="icon-organizations"></span>

View File

@@ -0,0 +1,2 @@
$("#<%= dom_id(@budget) %>_groups").html('<%= j render("admin/budgets/groups", groups: @groups) %>');
App.Forms.toggleLink();

View File

@@ -0,0 +1,2 @@
$("#<%= dom_id(@budget_group) %>").html('<%= j render("admin/budgets/group", group: @budget_group, headings: @headings) %>');
App.Forms.toggleLink();

View File

@@ -0,0 +1,77 @@
<h3><%= page_entries_info @investments %></h3>
<table>
<thead>
<tr>
<th><%= t("admin.budget_investments.index.table_id") %></th>
<th><%= t("admin.budget_investments.index.table_title") %></th>
<th><%= t("admin.budget_investments.index.table_admin") %></th>
<th><%= t("admin.budget_investments.index.table_valuator") %></th>
<th><%= t("admin.budget_investments.index.table_geozone") %></th>
<th><%= t("admin.budget_investments.index.table_feasibility") %></th>
<th class="text-center"><%= t("admin.budget_investments.index.table_valuation_finished") %></th>
<th class="text-center"><%= t("admin.budget_investments.index.table_selection") %></th>
</tr>
</thead>
<% @investments.each do |investment| %>
<tr id="<%= dom_id(investment) %>" class="budget_investment">
<td class="text-right">
<strong><%= investment.id %></strong>
</td>
<td>
<%= link_to investment.title, admin_budget_budget_investment_path(budget_id: @budget.id, id: investment.id, params: Budget::Investment.filter_params(params)) %>
</td>
<td class="small">
<% if investment.administrator.present? %>
<span title="<%= t('admin.budget_investments.index.assigned_admin') %>"><%= investment.administrator.name %></span>
<% else %>
<%= t("admin.budget_investments.index.no_admin_assigned") %>
<% end %>
</td>
<td class="small">
<% if investment.valuators.size == 0 %>
<%= t("admin.budget_investments.index.no_valuators_assigned") %>
<% else %>
<%= investment.valuators.collect(&:description_or_name).join(', ') %>
<% end %>
</td>
<td class="small">
<%= investment.heading.name %>
</td>
<td class="small">
<%= t("admin.budget_investments.index.feasibility.#{investment.feasibility}",
price: investment.formatted_price)
%>
</td>
<td class="small text-center">
<%= investment.valuation_finished? ? t('shared.yes'): t('shared.no') %>
</td>
<td class="small">
<% if investment.selected? %>
<%= link_to toggle_selection_admin_budget_budget_investment_path(@budget,
investment,
filter: params[:filter],
page: params[:page]),
method: :patch,
remote: true,
class: "button small expanded" do %>
<%= t("admin.budget_investments.index.selected") %>
<% end %>
<% elsif investment.feasible? && investment.valuation_finished? %>
<%= link_to toggle_selection_admin_budget_budget_investment_path(@budget,
investment,
filter: params[:filter],
page: params[:page]),
method: :patch,
remote: true,
class: "button small hollow expanded" do %>
<%= t("admin.budget_investments.index.select") %>
<% end %>
<% end %>
</td>
</tr>
<% end %>
</table>
<%= paginate @investments %>

View File

@@ -0,0 +1,36 @@
<div class="callout primary float-right">
<%= t "admin.budget_investments.show.info", budget_name: @budget.name, group_name: @investment.group.name, id: @investment.id %>
</div>
<br>
<h1 class="inline-block"><%= @investment.title %></h1>
<div class="row small-collapse spending-proposal-info">
<div class="small-12 medium-4 column">
<p title="<%= t("admin.budget_investments.show.group") %>: <%= @investment.group.name %>">
<strong><%= t("admin.budget_investments.show.heading") %>:</strong>
<%= @investment.heading.name %>
</p>
</div>
<div class="small-12 medium-4 column">
<p>
<strong><%= t("admin.budget_investments.show.by") %>:</strong>
<%= link_to @investment.author.name, admin_user_path(@investment.author) %>
</p>
</div>
<div class="small-12 medium-4 column">
<p>
<strong><%= t("admin.budget_investments.show.sent") %>:</strong>
<%= l @investment.created_at, format: :datetime %>
</p>
</div>
</div>
<% if @investment.external_url.present? %>
<p><%= text_with_links @investment.external_url %>&nbsp;<span class="icon-external small"></span></p>
<% end %>
<%= safe_html_with_links @investment.description %>

View File

@@ -0,0 +1,70 @@
<%= link_to admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)), class: 'back' do %>
<span class="icon-angle-left"></span> <%= t("shared.back") %>
<% end %>
<%= form_for @investment,
url: admin_budget_budget_investment_path(@budget, @investment) do |f| %>
<% Budget::Investment.filter_params(params).each do |filter_name, filter_value| %>
<%= hidden_field_tag filter_name, filter_value %>
<% end %>
<div class="row">
<div class="small-12 column">
<%= f.text_field :title, maxlength: Budget::Investment.title_max_length %>
</div>
<div class="ckeditor small-12 column">
<%= f.cktext_area :description, maxlength: Budget::Investment.description_max_length, ckeditor: { language: I18n.locale } %>
</div>
<div class="small-12 column">
<%= f.text_field :external_url %>
</div>
<div class="small-12 column">
<%= f.select :heading_id, budget_heading_select_options(@budget), include_blank: t("admin.budget_investments.edit.select_heading") %>
</div>
</div>
<h2 id="classification"><%= t("admin.budget_investments.edit.classification") %></h2>
<div class="row">
<div class="small-12 column">
<%= f.select(:administrator_id,
@admins.collect{ |a| [a.name_and_email, a.id ] },
{ include_blank: t("admin.budget_investments.edit.undefined") }) %>
</div>
<div class="small-12 column">
<%= f.label :tag_list, t("admin.budget_investments.edit.tags") %>
<div class="tags">
<% @tags.each do |tag| %>
<a class="js-add-tag-link"><%= tag.name %></a>
<% end %>
</div>
<%= f.text_field :valuation_tag_list,
value: @investment.tag_list_on(:valuation).sort.join(','),
label: false,
placeholder: t("admin.budget_investments.edit.tags_placeholder"),
class: 'js-tag-list' %>
</div>
<div class="small-12 column">
<%= f.label :valuator_ids, t("admin.budget_investments.edit.assigned_valuators") %>
<%= f.collection_check_boxes :valuator_ids, @valuators, :id, :email do |b| %>
<%= b.label(title: valuator_label(b.object)) { b.check_box + truncate(b.object.description_or_email, length: 60) } %>
<% end %>
</div>
</div>
<p class="clear">
<%= f.submit(class: "button", value: t("admin.budget_investments.edit.submit_button")) %>
</p>
<% end %>
<hr>
<%# render 'valuation/budget_investments/written_by_valuators' %>

View File

@@ -0,0 +1,44 @@
<h2 class="inline-block"><%= @budget.name %> - <%= t("admin.budget_investments.index.title") %></h2>
<div class="row margin">
<%= form_tag admin_budget_budget_investments_path(budget: @budget), method: :get, enforce_utf8: false do %>
<div class="small-12 medium-3 column">
<%= select_tag :administrator_id,
options_for_select(admin_select_options, params[:administrator_id]),
{ prompt: t("admin.budget_investments.index.administrator_filter_all"),
label: false,
class: "js-submit-on-change" } %>
</div>
<div class="small-12 medium-3 column">
<%= select_tag :valuator_id,
options_for_select(valuator_select_options, params[:valuator_id]),
{ prompt: t("admin.budget_investments.index.valuator_filter_all"),
label: false,
class: "js-submit-on-change" } %>
</div>
<div class="small-12 medium-3 column">
<%= select_tag :heading_id,
options_for_select(budget_heading_select_options(@budget), params[:heading_id]),
{ prompt: t("admin.budget_investments.index.heading_filter_all"),
label: false,
class: "js-submit-on-change" } %>
</div>
<div class="small-12 medium-3 column">
<%= select_tag :tag_name,
options_for_select(spending_proposal_tags_select_options, params[:tag_name]),
{ prompt: t("admin.budget_investments.index.tags_filter_all"),
label: false,
class: "js-submit-on-change" } %>
</div>
<% end %>
</div>
<%= render '/shared/filter_subnav', i18n_namespace: "admin.budget_investments.index" %>
<div id="investments">
<%= render '/admin/budget_investments/investments' %>
</div>

View File

@@ -0,0 +1,49 @@
<%= link_to admin_budget_budget_investments_path(Budget::Investment.filter_params(params)), data: {no_turbolink: true} do %>
<span class="icon-angle-left"></span> <%= t("shared.back") %>
<% end %>
<%= render 'written_by_author' %>
<%= link_to t("admin.budget_investments.show.edit"),
edit_admin_budget_budget_investment_path(@budget, @investment,
Budget::Investment.filter_params(params)) %>
<hr>
<h2 id="classification"><%= t("admin.budget_investments.show.classification") %></h2>
<p><strong><%= t("admin.budget_investments.show.assigned_admin") %>:</strong>
<%= @investment.administrator.try(:name_and_email) || t("admin.budget_investments.show.undefined") %>
</p>
<p id="tags">
<strong><%= t("admin.budget_investments.show.tags") %>:</strong>
<%= @investment.tags_on(:valuation).pluck(:name).join(', ') %>
</p>
<p id="assigned_valuators">
<strong><%= t("admin.budget_investments.show.assigned_valuators") %>:</strong>
<% if @investment.valuators.any? %>
<%= @investment.valuators.collect(&:name_and_email).join(', ') %>
<% else %>
<%= t("admin.budget_investments.show.undefined") %>
<% end %>
</p>
<p>
<%= link_to t("admin.budget_investments.show.edit_classification"),
edit_admin_budget_budget_investment_path(@budget, @investment,
{anchor: 'classification'}.merge(Budget::Investment.filter_params(params))) %>
</p>
<hr>
<h2><%= t("admin.budget_investments.show.dossier") %></h2>
<%= render 'valuation/budget_investments/written_by_valuators' %>
<p>
<%= link_to t("admin.budget_investments.show.edit_dossier"), edit_valuation_budget_budget_investment_path(@budget, @investment) %>
</p>

View File

@@ -0,0 +1 @@
$("#investments").html('<%= j render("admin/budget_investments/investments") %>');

View File

@@ -0,0 +1,20 @@
<%= form_for [:admin, @budget] do |f| %>
<%= f.text_field :name, maxlength: Budget.title_max_length %>
<% Budget::PHASES.each do |phase| %>
<div class="margin-top">
<%= f.cktext_area "description_#{phase}", maxlength: Budget.description_max_length, ckeditor: { language: I18n.locale } %>
</div>
<% end %>
<div class="row margin-top">
<div class="small-12 medium-9 column">
<%= f.select :phase, budget_phases_select_options %>
</div>
<div class="small-12 medium-3 column">
<%= f.select :currency_symbol, budget_currency_symbol_select_options %>
</div>
</div>
<%= f.submit nil, class: "button success" %>
<% end %>

View File

@@ -0,0 +1,67 @@
<table>
<thead>
<tr>
<th colspan="3" class="with-button">
<%= group.name %>
<%= link_to t("admin.budgets.form.add_heading"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#group-#{group.id}-new-heading-form" } %>
</th>
</tr>
<% if headings.blank? %>
<tbody>
<tr>
<td colspan="3">
<div class="callout primary">
<%= t("admin.budgets.form.no_heading") %>
</div>
</td>
</tr>
<% else %>
<tr>
<th><%= t("admin.budgets.form.table_heading") %></th>
<th><%= t("admin.budgets.form.table_amount") %></th>
</tr>
</thead>
<tbody>
<% end %>
<!-- new heading form -->
<tr id="group-<%= group.id %>-new-heading-form" style="display:none">
<td colspan="3">
<%= form_for [:admin, @budget, group, Budget::Heading.new], remote: true do |f| %>
<label><%= t("admin.budgets.form.heading") %></label>
<%= f.text_field :name,
label: false,
maxlength: 50,
placeholder: t("admin.budgets.form.heading") %>
<div class="row">
<div class="small-12 medium-6 column">
<label><%= t("admin.budgets.form.amount") %></label>
<%= f.text_field :price,
label: false,
maxlength: 8,
placeholder: t("admin.budgets.form.amount") %>
</div>
</div>
<%= f.submit t("admin.budgets.form.save_heading"), class: "button success" %>
<% end %>
</td>
</tr>
<!-- /. new heading form -->
<!-- headings list -->
<% headings.each do |heading| %>
<tr>
<td>
<%= heading.name %>
</td>
<td>
<%= heading.price %>
</td>
</tr>
<% end %>
<!-- /. headings list -->
</tbody>
</table>
</div>

View File

@@ -0,0 +1,32 @@
<h3 class="inline-block"><%= t('admin.budgets.show.groups', count: groups.count) %></h3>
<% if groups.blank? %>
<div class="callout primary">
<%= t("admin.budgets.form.no_groups") %>
<strong><%= link_to t("admin.budgets.form.add_group"), "#",
class: "js-toggle-link",
data: { "toggle-selector" => "#new-group-form" } %></strong>
</div>
<% else %>
<%= link_to t("admin.budgets.form.add_group"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#new-group-form" } %>
<% end %>
<%= form_for [:admin, @budget, Budget::Group.new], html: {id: "new-group-form", style: "display:none"}, remote: true do |f| %>
<div class="input-group">
<span class="input-group-label">
<label><%= f.label :name,t("admin.budgets.form.group") %></label>
</span>
<%= f.text_field :name,
label: false,
maxlength: 50,
placeholder: t("admin.budgets.form.group") %>
<div class="input-group-button">
<%= f.submit t("admin.budgets.form.create_group"), class: "button success" %>
</div>
</div>
<% end %>
<% groups.each do |group| %>
<div id="<%= dom_id(group) %>">
<%= render "admin/budgets/group", group: group, headings: group.headings %>
</div>
<% end %>

View File

@@ -0,0 +1,9 @@
<%= back_link_to admin_budgets_path %>
<div class="row">
<div class="small-12 medium-9 column">
<h2><%= t("admin.budgets.edit.title") %></h2>
<%= render '/admin/budgets/form' %>
</div>
</div>

View File

@@ -0,0 +1,44 @@
<h2 class="inline-block"><%= t("admin.budgets.index.title") %></h2>
<%= link_to t("admin.budgets.index.new_link"),
new_admin_budget_path,
class: "button float-right margin-right" %>
<%= render 'shared/filter_subnav', i18n_namespace: "admin.budgets.index" %>
<h3><%= page_entries_info @budgets %></h3>
<table>
<thead>
<tr>
<th><%= t("admin.budgets.index.table_name") %></th>
<th><%= t("admin.budgets.index.table_phase") %></th>
<th><%= t("admin.budgets.index.table_investments") %></th>
<th><%= t("admin.budgets.index.table_edit_groups") %></th>
<th><%= t("admin.budgets.index.table_edit_budget") %></th>
</tr>
</thead>
<tbody>
<% @budgets.each do |budget| %>
<tr id="<%= dom_id(budget) %>" class="budget">
<td>
<%= budget.name %>
</td>
<td class="small">
<%= t("budgets.phase.#{budget.phase}") %>
</td>
<td class="small">
<%= link_to t("admin.budgets.index.budget_investments"), admin_budget_budget_investments_path(budget_id: budget.id) %>
</td>
<td class="small">
<%= link_to t("admin.budgets.index.edit_groups"), admin_budget_path(budget) %>
</td>
<td class="small">
<%= link_to t("admin.budgets.index.edit_budget"), edit_admin_budget_path(budget) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= paginate @budgets %>

View File

@@ -0,0 +1,7 @@
<div class="row">
<div class="small-12 medium-9 column">
<h2><%= t("admin.budgets.new.title") %></h2>
<%= render '/admin/budgets/form' %>
</div>
</div>

View File

@@ -0,0 +1,7 @@
<%= back_link_to admin_budgets_path %>
<h2><%= @budget.name %></h2>
<div id="<%= dom_id @budget %>_groups">
<%= render "groups", groups: @budget.groups %>
</div>

View File

@@ -0,0 +1,25 @@
<%= form_for(Budget::Investment.new, url: url, as: :budget_investment, method: :get) do |f| %>
<div class="row">
<div class="small-12 medium-6 column">
<%= text_field_tag :search, "" %>
</div>
<div class="small-12 medium-6 column">
<%= select_tag :heading_id,
options_for_select(budget_heading_select_options(@budget),
params[:heading_id]),
include_blank: true
%>
</div>
</div>
<div class="row">
<div class="small-12 medium-6 column">
<%= check_box_tag :unfeasible, "1", params[:unfeasible].present? %>
</div>
</div>
<div class="row">
<div class="form-inline small-12 medium-3 column end">
<%= f.submit t("shared.search"), class: "button" %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,54 @@
<div class="expanded budget no-margin-top padding">
<div class="row">
<%= render 'shared/back_link' %>
<h1 class="text-center"><%= t("budgets.ballots.show.title") %></h1>
<div class="small-12 medium-8 column small-centered text-center">
<h2>
<%= t("budgets.ballots.show.voted_html",
count: @ballot.investments.count) %>
</h2>
<p>
<small>
<%= t("budgets.ballots.show.voted_info_html") %>
</small>
</p>
</div>
</div>
</div>
<div class="row ballot">
<div class="margin-top">
<% @ballot.groups.each do |group| %>
<div id="<%= dom_id(group) %>"
class="small-12 medium-6 column">
<h3 class="subtitle">
<%= group.name %> - <%= group.headings.first.name %>
</h3>
<% if @ballot.has_lines_in_group?(group) %>
<h4 class="amount-spent text-right">
<%= t("budgets.ballots.show.amount_spent") %>
<span>
<%= @ballot.formatted_amount_spent(@ballot.heading_for_group(group)) %>
</span>
</h4>
<% else %>
<p>
<%= t("budgets.ballots.show.zero") %><br>
</p>
<% end %>
<ul class="ballot-list">
<%= render partial: 'budgets/ballot/investment',
collection: @ballot.investments.by_group(group.id) %>
</ul>
<h4>
<%= t("budgets.ballots.show.remaining",
amount: @ballot.formatted_amount_available(@ballot.heading_for_group(group))).html_safe %>
</h4>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<li id="<%= dom_id(investment) %>">
<%= link_to investment.title, budget_investment_path(@budget, investment) %>
<span><%= investment.formatted_price %></span>
<% if @budget.balloting? %>
<%= link_to budget_ballot_line_path(@budget, id: investment.id),
title: t('budgets.ballots.show.remove'),
class: "remove-investment-project",
method: :delete,
remote: true do %>
<span class="icon-x"></span>
<% end %>
<% end %>
</li>

View File

@@ -0,0 +1,16 @@
<li id="<%= dom_id(investment) %>_sidebar">
<%= investment.title %>
<span><%= investment.formatted_price %></span>
<% if @budget.balloting? %>
<%= link_to budget_ballot_line_url(id: investment.id,
investments_ids: investment_ids),
title: t('budgets.ballots.show.remove'),
class: "remove-investment-project",
method: :delete,
remote: true do %>
<span class="sr-only"><%= t('budgets.ballots.show.remove') %></span>
<span class="icon-x delete"></span>
<% end %>
<% end %>
</li>

View File

@@ -0,0 +1,29 @@
<span class="total-amount">
<%= @budget.formatted_heading_price(@heading) %>
</span>
<div class="progress" role="progressbar" tabindex="0"
id="progress"
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
<div class="progress-meter"
style="width:
<%= progress_bar_width(@budget.heading_price(@heading),
@ballot.amount_spent(@heading)) %>">
</div>
</div>
<div class="progress spent-amount-progress" role="progressbar" tabindex="0"
aria-valuenow="20" aria-valuemin="0" aria-valuetext="25 percent" aria-valuemax="100">
<span class="progress-meter spent-amount-meter"
style="width:
<%= progress_bar_width(@budget.heading_price(@heading),
@ballot.amount_spent(@heading)) %>">
<p id="amount-spent" class="progress-meter-text spent-amount-text">
<%= @ballot.formatted_amount_spent(@heading) %>
<span id="amount-available" class="amount-available">
<%= t("budgets.progress_bar.available") %>
<span><%= @ballot.formatted_amount_available(@heading) %></span>
</span>
</p>
</span>
</div>

View File

@@ -0,0 +1,8 @@
<% if @investments.present? %>
<% @investments.each do |investment| %>
$("#<%= dom_id(investment) %>_ballot").html('<%= j render("/budgets/investments/ballot",
investment: investment,
investment_ids: investment_ids,
ballot: ballot) %>');
<% end %>
<% end %>

View File

@@ -0,0 +1,11 @@
$("#progress_bar").html('<%= j render("/budgets/ballot/progress_bar", ballot: @ballot) %>');
$("#sidebar").html('<%= j render("/budgets/investments/sidebar") %>');
$("#<%= dom_id(@investment) %>_ballot").html('<%= j render("/budgets/investments/ballot",
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot) %>');
<%= render 'refresh_ballots',
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot %>

View File

@@ -0,0 +1,12 @@
$("#progress_bar").html('<%= j render("budgets/ballot/progress_bar", ballot: @ballot) %>');
$("#sidebar").html('<%= j render("budgets/investments/sidebar") %>');
$("#ballot").html('<%= j render("budgets/ballot/ballot") %>')
$("#<%= dom_id(@investment) %>_ballot").html('<%= j render("/budgets/investments/ballot",
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot) %>');
<%= render 'refresh_ballots',
investment: @investment,
investment_ids: @investment_ids,
ballot: @ballot %>

View File

@@ -0,0 +1,2 @@
$("#<%= dom_id(@spending_proposal) %>_ballot").html('<%= j render("spending_proposals/ballot", spending_proposal: @spending_proposal) %>');
$(".no-supports-allowed").show();

View File

@@ -0,0 +1,3 @@
<div id="ballot">
<%= render "budgets/ballot/ballot" %>
</div>

View File

@@ -0,0 +1,49 @@
<div class="expanded budget no-margin-top">
<div class="row">
<div class="small-12 medium-9 column padding">
<%= back_link_to budget_path(@budget) %>
<h2><%= t("budgets.groups.show.title") %></h2>
</div>
</div>
</div>
<% if params[:unfeasible] %>
<div class="row margin-top">
<div class="small-12 column">
<h3><%= t("budgets.groups.show.unfeasible_title") %></h3>
</div>
</div>
<% end %>
<div class="row margin-top">
<div id="select-district" class="small-12 medium-7 column select-district">
<div class="row">
<% @group.headings.each_slice(7) do |slice| %>
<div class="small-6 medium-4 column end">
<% slice.each do |heading| %>
<span id="<%= dom_id(heading) %>"
class="<%= css_for_ballot_heading(heading) %>">
<%= link_to heading.name,
budget_investments_path(heading_id: heading.id,
unfeasible: params[:unfeasible]),
data: { no_turbolink: true } %><br>
</span>
<% end %>
</div>
<% end %>
</div>
</div>
<div class="medium-5 column show-for-medium text-center">
<%= image_tag "map.jpg" %>
</div>
</div>
<% if params[:unfeasible].blank? %>
<div class="row margin-top">
<div class="small-12 column">
<small><%= link_to t("budgets.groups.show.unfeasible"),
budget_path(@budget, unfeasible: 1) %></small>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,30 @@
<div class="expanded budget no-margin-top padding">
<div class="row">
<div class="small-12 column">
<h1><%= t('budgets.index.title') %></h1>
</div>
</div>
</div>
<div class="row margin-top">
<div class="small-12 medium-9 column">
<table>
<thead>
<th><%= Budget.human_attribute_name(:name) %></th>
<th><%= Budget.human_attribute_name(:phase) %></th>
</thead>
<tbody>
<% @budgets.each do |budget| %>
<tr>
<td>
<%= link_to budget.name, budget %>
</td>
<td>
<%= budget.translated_phase %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>

View File

@@ -0,0 +1,59 @@
<% reason = investment.reason_for_not_being_ballotable_by(current_user, ballot) %>
<div class="js-participation supports ballot">
<% if ballot.has_investment?(investment) %>
<div class="remove supported inline-block">
<span class="icon-check-circle"
title="<%= t("budgets.investments.investment.already_added") %>">
</span>
<p class="investment-project-amount">
<%= investment.formatted_price %>
</p>
<% if investment.should_show_ballots? %>
<%= link_to t('budgets.ballots.show.remove'),
budget_ballot_line_path(id: investment.id,
budget_id: investment.budget_id,
investments_ids: investment_ids),
class: "delete small expanded",
method: :delete,
remote: true %>
<% end %>
</div>
<% else %>
<div class="add in-favor">
<p class="investment-project-amount">
<%= investment.formatted_price %>
</p>
<% if investment.should_show_ballots? %>
<%= link_to t("budgets.investments.investment.add"),
budget_ballot_lines_url(investment_id: investment.id,
budget_id: investment.budget_id,
investments_ids: investment_ids),
class: "button button-support small expanded",
title: t('budgets.investments.investment.support_title'),
method: :post,
remote: true %>
<% end %>
</div>
<% end %>
<% if reason.present? && !ballot.has_investment?(investment) %>
<div class="js-participation-not-allowed participation-not-allowed" style='display:none'>
<p>
<%= t("budgets.ballots.reasons_for_not_balloting.#{reason}",
verify_account: link_to(t("votes.verify_account"), verification_path),
signin: link_to(t("votes.signin"), new_user_session_path),
signup: link_to(t("votes.signup"), new_user_registration_path),
my_heading: link_to(investment.heading.name,
budget_investments_path(budget_id: investment.budget_id,
heading_id: investment.heading_id))
).html_safe %>
</p>
</div>
<% end %>
</div>

View File

@@ -0,0 +1,11 @@
<div class="sidebar-divider"></div>
<h2 class="sidebar-title"><%= t("shared.tags_cloud.categories") %></h2>
<br>
<ul id="categories" class="no-bullet categories">
<% @categories.each do |category| %>
<li class="inline-block">
<%= link_to category.name,
budget_investments_path(@budget, search: category.name) %></li>
<% end %>
</ul>

View File

@@ -0,0 +1,11 @@
<div class="sidebar-divider"></div>
<h2 class="sidebar-title"><%= t("budgets.investments.index.sidebar.by_feasibility") %></h2>
<br>
<% if params[:unfeasible].present? %>
<%= link_to t("budgets.investments.index.sidebar.feasible"),
budget_investments_path(@budget, heading_id: @heading, unfeasible: nil) %>
<% else %>
<%= link_to t("budgets.investments.index.sidebar.unfeasible"),
budget_investments_path(@budget, heading_id: @heading, unfeasible: 1) %>
<% end %>

View File

@@ -0,0 +1,69 @@
<%= form_for(@investment, url: form_url, method: :post) do |f| %>
<%= render 'shared/errors', resource: @investment %>
<div class="row">
<div class="small-12 medium-8 column">
<%= f.select :heading_id, budget_heading_select_options(@budget), {include_blank: true, } %>
</div>
<div class="small-12 column">
<%= f.text_field :title, maxlength: SpendingProposal.title_max_length %>
</div>
<%= f.invisible_captcha :subtitle %>
<div class="ckeditor small-12 column">
<%= f.cktext_area :description, maxlength: SpendingProposal.description_max_length, ckeditor: { language: I18n.locale } %>
</div>
<div class="small-12 column">
<%= f.text_field :external_url %>
</div>
<div class="small-12 column">
<%= f.text_field :location %>
</div>
<div class="small-12 column">
<%= f.text_field :organization_name %>
</div>
<div class="small-12 column">
<%= f.label :tag_list, t("budgets.investments.form.tags_label") %>
<p class="note"><%= t("budgets.investments.form.tags_instructions") %></p>
<div id="category_tags" class="tags">
<%= f.label :category_tag_list, t("budgets.investments.form.tag_category_label") %>
<% @categories.each do |tag| %>
<a class="js-add-tag-link"><%= tag.name %></a>
<% end %>
</div>
<br>
<%= f.text_field :tag_list, value: @investment.tag_list.to_s,
label: false,
placeholder: t("budgets.investments.form.tags_placeholder"),
class: 'js-tag-list' %>
</div>
<% unless current_user.manager? %>
<div class="small-12 column">
<%= f.label :terms_of_service do %>
<%= f.check_box :terms_of_service, title: t('form.accept_terms_title'), label: false %>
<span class="checkbox">
<%= t("form.accept_terms",
policy: link_to(t("form.policy"), "/privacy", target: "blank"),
conditions: link_to(t("form.conditions"), "/conditions", target: "blank")).html_safe %>
</span>
<% end %>
</div>
<% end %>
<div class="actions small-12 medium-6 large-4 end column">
<%= f.submit(nil, class: "button expanded") %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,66 @@
<% if @heading.present? %>
<section class="no-margin-top margin-bottom">
<div class="expanded jumbo-budget budget-heading padding">
<div class="row">
<div class="small-12 column">
<% if @heading.group.headings.count == 1 %>
<%= back_link_to budget_path(@budget, unfeasible: params[:unfeasible]) %>
<% else %>
<%= back_link_to budget_group_path(@budget,
@heading.group,
unfeasible: params[:unfeasible]) %>
<% end %>
<% if can? :show, @ballot %>
<%= link_to t("budgets.investments.header.check_ballot"),
budget_ballot_path(@budget),
class: "button float-right" %>
<% end %>
</div>
</div>
<div class="row progress-votes">
<div class="small-12 column">
<% if can? :show, @ballot %>
<div id="check-ballot" style="display: none;">
<%= link_to t("budgets.investments.header.check_ballot"),
budget_ballot_path(@budget) %>
</div>
<% if @ballot.valid_heading?(@heading) %>
<div class="progress-bar-nav" data-fixed-bar>
<h1><%= @heading.name %></h1>
<div id="progress_bar" class="no-margin-top">
<%= render 'budgets/ballot/progress_bar' %>
</div>
</div>
<% else %>
<h1><%= @heading.name %></h1>
<div class="small-12 medium-9">
<div class="callout warning">
<%= t("budgets.investments.header.different_heading_assigned_html",
heading_link: link_to(
@assigned_heading.name,
budget_investments_path(@budget, heading: @assigned_heading))
) %>
</div>
</div>
<% end %>
<% else %>
<h1 class="margin-top"><%= @heading.name %></h1>
<h2><%= @budget.formatted_heading_price(@heading) %></h2>
<% end %>
</div>
</div>
</div>
</section>
<% else %>
<div class="expanded jumbo-budget padding no-margin-top margin-bottom">
<div class="row">
<div class="small-12 column">
<%= back_link_to budget_path(@budget) %>
<h1><%= t('budgets.investments.index.title') %></h1>
</div>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,81 @@
<div id="<%= dom_id(investment) %>" class="budget-investment clear">
<div class="panel">
<div class="row">
<div class="small-12 medium-9 column">
<div class="budget-investment-content">
<% cache [locale_and_user_status(investment), 'index', investment, investment.author] do %>
<span class="label-budget-investment float-left"><%= t("budgets.investments.investment.title") %></span>
<span class="icon-budget"></span>
<h3><%= link_to investment.title, namespaced_budget_investment_path(investment) %></h3>
<p class="investment-project-info">
<%= l investment.created_at.to_date %>
<% if investment.author.hidden? || investment.author.erased? %>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<span class="author">
<%= t("budgets.investments.show.author_deleted") %>
</span>
<% else %>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<span class="author">
<%= investment.author.name %>
</span>
<% if investment.author.official? %>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<span class="label round level-<%= investment.author.official_level %>">
<%= investment.author.official_position %>
</span>
<% end %>
<% end %>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<%= investment.heading.name %>
</p>
<div class="investment-project-description">
<p><%= investment.description %></p>
<div class="truncate"></div>
</div>
<%= render "shared/tags", taggable: investment, limit: 5 %>
<% end %>
</div>
</div>
<% unless investment.unfeasible? %>
<% if investment.should_show_votes? %>
<div id="<%= dom_id(investment) %>_votes"
class="small-12 medium-3 column text-center">
<%= render partial: '/budgets/investments/votes', locals: {
investment: investment,
investment_votes: investment_votes,
vote_url: namespaced_budget_investment_vote_path(investment, value: 'yes')
} %>
</div>
<% elsif investment.should_show_vote_count? %>
<div id="<%= dom_id(investment) %>_votes"
class="small-12 medium-3 column text-center">
<div class="supports js-participation">
<span class="total-supports no-button">
<%= t("budgets.investments.investment.supports",
count: investment.total_votes) %>
</span>
</div>
</div>
<% elsif investment.should_show_ballots? %>
<div id="<%= dom_id(investment) %>_ballot"
class="small-12 medium-3 column text-center">
<%= render partial: '/budgets/investments/ballot', locals: {
investment: investment,
investment_ids: investment_ids,
ballot: ballot
} %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>

View File

@@ -0,0 +1,113 @@
<section class="budget-investment-show" id="<%= dom_id(investment) %>">
<div class="row">
<div class="small-12 medium-9 column">
<%= back_link_to budget_investments_path(investment.budget) %>
<h1><%= investment.title %></h1>
<div class="budget-investment-info">
<%= render '/shared/author_info', resource: investment %>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<%= l investment.created_at.to_date %>
<span class="bullet">&nbsp;&bull;&nbsp;</span>
<%= investment.heading.name %>
</div>
<br>
<p id="investment_code">
<%= t("budgets.investments.show.code_html", code: investment.id) %>
</p>
<% if investment.location.present? %>
<p>
<%= t("budgets.investments.show.location_html", location: investment.location) %>
</p>
<% end %>
<% if investment.organization_name.present? %>
<p>
<%= t("budgets.investments.show.organization_name_html", name: investment.organization_name) %>
</p>
<% end %>
<%= render 'shared/tags', taggable: investment %>
<%= safe_html_with_links investment.description.html_safe %>
<% if investment.external_url.present? %>
<div class="document-link">
<%= text_with_links investment.external_url %>
</div>
<% end %>
<% if investment.unfeasible? && investment.unfeasibility_explanation.present? %>
<h2><%= t('budgets.investments.show.unfeasibility_explanation') %></h2>
<p><%= investment.unfeasibility_explanation %></p>
<% end %>
<% if investment.feasible? && investment.price_explanation.present? %>
<h2><%= t('budgets.investments.show.price_explanation') %></h2>
<p><%= investment.price_explanation %></p>
<% end %>
</div>
<aside class="small-12 medium-3 column">
<% if investment.should_show_aside? %>
<% if investment.should_show_votes? %>
<div class="sidebar-divider"></div>
<h2><%= t("budgets.investments.show.supports") %></h2>
<div class="text-center">
<div id="<%= dom_id(investment) %>_votes">
<%= render partial: '/budgets/investments/votes', locals: {
investment: investment,
investment_votes: investment_votes,
vote_url: vote_budget_investment_path(investment.budget, investment, value: 'yes')
} %>
</div>
</div>
<% elsif investment.should_show_vote_count? %>
<div class="sidebar-divider"></div>
<h2><%= t("budgets.investments.show.supports") %></h2>
<div class="text-center">
<span class="total-supports">
<%= t("budgets.investments.investment.supports",
count: investment.total_votes) %>
</span>
</div>
<% elsif investment.should_show_ballots? %>
<div class="sidebar-divider"></div>
<h2><%= t("budgets.investments.show.votes") %></h2>
<div class="text-center">
<div id="<%= dom_id(investment) %>_ballot">
<%= render partial: 'ballot', locals: {
investment: investment,
investment_ids: investment_ids,
ballot: ballot,
} %>
</div>
</div>
<% end %>
<% else %>
<div class="float-right">
<span class="label-budget-investment float-left">
<%= t("budgets.investments.show.title") %>
</span>
<span class="icon-budget"></span>
</div>
<br>
<% end %>
<div id="social-share" class="sidebar-divider"></div>
<h2><%= t("budgets.investments.show.share") %></h2>
<div class="social-share-full">
<%= social_share_button_tag("#{investment.title} #{setting['twitter_hashtag']}") %>
<% if browser.device.mobile? %>
<a href="whatsapp://send?text=<%= investment.title %> <%= budget_investment_url(budget_id: investment.budget_id, id: investment.id) %>" data-action="share/whatsapp/share">
<span class="icon-whatsapp whatsapp"></span>
</a>
<% end %>
</div>
</aside>
</div>
</section>

View File

@@ -0,0 +1,52 @@
<div class="clear"></div>
<% if can?(:create, Budget::Investment.new(budget: @budget)) %>
<% if current_user && current_user.level_two_or_three_verified? %>
<%= link_to t("budgets.investments.index.sidebar.create"),
new_budget_investment_path, class: "button budget expanded" %>
<% else %>
<div class="callout warning">
<%= t("budgets.investments.index.sidebar.verified_only",
verify: link_to(t("budgets.investments.index.sidebar.verify_account"),
verification_path)).html_safe %>
</div>
<% end %>
<% end %>
<%= render "shared/tag_cloud", taggable: 'budget/investment' %>
<%= render 'budgets/investments/categories' %>
<% if @heading && can?(:show, @ballot) %>
<div class="sidebar-divider"></div>
<h2 class="sidebar-title">
<%= t("budgets.investments.index.sidebar.my_ballot") %>
</h2>
<% if @ballot.investments.by_heading(@heading.id).count > 0 %>
<p>
<em>
<%= t("budgets.investments.index.sidebar.voted_html",
count: @ballot.investments.by_heading(@heading.id).count,
amount_spent: @ballot.formatted_amount_spent(@heading)) %>
</em>
</p>
<% else %>
<p><strong><%= t("budgets.investments.index.sidebar.zero") %></strong></p>
<% end %>
<ul class="ballot-list">
<% if @heading %>
<% @ballot.investments.by_heading(@heading.id).each do |investment| %>
<%= render 'budgets/ballot/investment_for_sidebar',
investment: investment,
investment_ids: @investment_ids %>
<% end %>
<% end %>
</ul>
<p class="callout primary">
<%= t("budgets.investments.index.sidebar.voted_info") %>
</p>
<% end %>

View File

@@ -0,0 +1,46 @@
<% reason = investment.reason_for_not_being_selectable_by(current_user) %>
<% voting_allowed = true unless reason.presence == :not_voting_allowed %>
<% user_voted_for = voted_for?(investment_votes, investment) %>
<div class="supports js-participation">
<span class="total-supports <%= 'no-button' unless voting_allowed || user_voted_for %>">
<%= t("budgets.investments.investment.supports", count: investment.total_votes) %>
</span>
<div class="in-favor js-in-favor">
<% if user_voted_for %>
<div class="supported callout success">
<%= t("budgets.investments.investment.already_supported") %>
</div>
<% elsif investment.should_show_votes? %>
<%= link_to vote_url,
class: "button button-support small expanded",
title: t('budgets.investments.investment.support_title'),
method: "post",
remote: (current_user && current_user.voted_in_group?(investment.group) ? true : false),
data: (current_user && current_user.voted_in_group?(investment.group) ? nil : { confirm: t('budgets.investments.investment.confirm_group')} ),
"aria-hidden" => css_for_aria_hidden(reason) do %>
<%= t("budgets.investments.investment.give_support") %>
<% end %>
<% end %>
</div>
<% if reason.present? && !user_voted_for %>
<div class="js-participation-not-allowed participation-not-allowed" style='display:none' aria-hidden="false">
<p>
<%= t("votes.budget_investments.#{reason}",
verify_account: link_to(t("votes.verify_account"), verification_path),
signin: link_to(t("votes.signin"), new_user_session_path),
signup: link_to(t("votes.signup"), new_user_registration_path)
).html_safe %>
</p>
</div>
<% end %>
<% if user_voted_for && setting['twitter_handle'] %>
<div class="share-supported">
<%= social_share_button_tag("#{investment.title} #{setting['twitter_hashtag']}", url: budget_investment_url(budget_id: investment.budget_id, id: investment.id), via: setting['twitter_handle']) %>
</div>
<% end %>
</div>

Some files were not shown because too many files have changed in this diff Show More