Majority test for motions
This commit is contained in:
parent
447339ec33
commit
e5e1e3e8ba
@ -12,7 +12,8 @@ INPUT_TYPE_MAPPING = {
|
|||||||
'choice': str,
|
'choice': str,
|
||||||
'comments': list,
|
'comments': list,
|
||||||
'colorpicker': str,
|
'colorpicker': str,
|
||||||
'datetimepicker': int}
|
'datetimepicker': int,
|
||||||
|
'float': float}
|
||||||
|
|
||||||
|
|
||||||
class ConfigHandler:
|
class ConfigHandler:
|
||||||
|
@ -429,6 +429,7 @@ angular.module('OpenSlidesApp.core.site', [
|
|||||||
string: 'text',
|
string: 'text',
|
||||||
text: 'textarea',
|
text: 'textarea',
|
||||||
integer: 'number',
|
integer: 'number',
|
||||||
|
float: 'number',
|
||||||
boolean: 'checkbox',
|
boolean: 'checkbox',
|
||||||
choice: 'choice',
|
choice: 'choice',
|
||||||
comments: 'comments',
|
comments: 'comments',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
|
|
||||||
from openslides.core.config import ConfigVariable
|
from openslides.core.config import ConfigVariable
|
||||||
|
|
||||||
@ -186,6 +186,17 @@ def get_config_variables():
|
|||||||
group='Motions',
|
group='Motions',
|
||||||
subgroup='Voting and ballot papers')
|
subgroup='Voting and ballot papers')
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name='motions_poll_default_quorum',
|
||||||
|
default_value=50,
|
||||||
|
input_type='float',
|
||||||
|
label='Quorum for Majority tests',
|
||||||
|
help_text='Default percentage that must be surpassed for a motion to be successfull',
|
||||||
|
weight=357,
|
||||||
|
group='Motions',
|
||||||
|
subgroup='Voting and ballot papers',
|
||||||
|
validators=(MinValueValidator(0), MaxValueValidator(100),))
|
||||||
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name='motions_pdf_ballot_papers_selection',
|
name='motions_pdf_ballot_papers_selection',
|
||||||
default_value='CUSTOM_NUMBER',
|
default_value='CUSTOM_NUMBER',
|
||||||
|
@ -479,6 +479,86 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
// child controller of MotionDetailCtrl for each single poll.
|
||||||
|
// TODO for now it is ust needed for majority Tests, which may be moved to a more generic
|
||||||
|
// place later
|
||||||
|
.controller('MotionPollDetailCtrl', [
|
||||||
|
'$scope',
|
||||||
|
'Config',
|
||||||
|
function($scope, Config) {
|
||||||
|
$scope.base = Config.get('motions_poll_100_percent_base').value;
|
||||||
|
$scope.basechoices = [{'value': 'YES_NO_ABSTAIN', 'display_name': 'Yes/No/Abstain'},
|
||||||
|
{'value': 'YES_NO', 'display_name': 'Yes/No'},
|
||||||
|
{'value': 'VALID', 'display_name': 'All valid ballots'},
|
||||||
|
{'value': 'CAST', 'display_name': 'All casted ballots'},
|
||||||
|
{'value': 'DISABLED', 'display_name': 'Disabled (no percents)'}];
|
||||||
|
$scope.quorum = Config.get('motions_poll_default_quorum').value;
|
||||||
|
$scope.isPossible = function() {
|
||||||
|
if ($scope.base == 'CAST' && $scope.poll.votescast > 0) {
|
||||||
|
return true;
|
||||||
|
} else if ($scope.base == 'VALID' && $scope.poll.votesvalid > 0) {
|
||||||
|
return true;
|
||||||
|
} else if ($scope.base == 'YES_NO_ABSTAIN' &&
|
||||||
|
(!$scope.poll.yes || $scope.poll.yes >= 0) &&
|
||||||
|
(!$scope.poll.no ||$scope.poll.no >= 0) &&
|
||||||
|
(!$scope.poll.abstain || $scope.poll.abstain >= 0) &&
|
||||||
|
($scope.poll.yes + $scope.poll.no + $scope.poll.abstain > 0)) {
|
||||||
|
return true;
|
||||||
|
} else if ($scope.base == 'YES_NO' &&
|
||||||
|
(!$scope.poll.yes || $scope.poll.yes >= 0) &&
|
||||||
|
(!$scope.poll.no ||$scope.poll.no >= 0) &&
|
||||||
|
($scope.poll.yes + $scope.poll.no > 0)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// returns an integer. 0 and positive numbers indicate a success and the amount of votes
|
||||||
|
// in excess, negative numbers are a failure (amount of missing votes), or null in case of error
|
||||||
|
$scope.isReached = function() {
|
||||||
|
var basenr;
|
||||||
|
if ($scope.base == 'CAST' && $scope.poll.votescast > 0) {
|
||||||
|
basenr = $scope.poll.votescast;
|
||||||
|
} else if ($scope.base == 'VALID' && $scope.poll.votesvalid > 0) {
|
||||||
|
basenr = $scope.poll.votesvalid;
|
||||||
|
} else if ($scope.base == 'YES_NO') {
|
||||||
|
basenr = 0;
|
||||||
|
if ($scope.poll.yes > 0) {
|
||||||
|
basenr = $scope.poll.yes;
|
||||||
|
}
|
||||||
|
if ($scope.poll.no > 0) {
|
||||||
|
basenr = basenr + $scope.poll.no;
|
||||||
|
}
|
||||||
|
} else if ($scope.base == 'YES_NO_ABSTAIN') {
|
||||||
|
basenr = 0;
|
||||||
|
if ($scope.poll.yes > 0) {
|
||||||
|
basenr = $scope.poll.yes;
|
||||||
|
}
|
||||||
|
if ($scope.poll.no > 0) {
|
||||||
|
basenr = basenr + $scope.poll.no;
|
||||||
|
}
|
||||||
|
if ($scope.poll.abstain > 0) {
|
||||||
|
basenr = basenr + $scope.poll.abstain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (basenr > 0) {
|
||||||
|
var needed = Math.ceil(basenr / 100 * $scope.quorum);
|
||||||
|
if ((basenr / 100 * $scope.quorum) % 1 === 0) {
|
||||||
|
//the quorum is exactly reached, not passed
|
||||||
|
needed = needed + 1;
|
||||||
|
}
|
||||||
|
if ($scope.poll.yes >= 0) {
|
||||||
|
var result = $scope.poll.yes - needed;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 'undefined';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//isPlausible: TODO: check if the sums match up
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
.controller('MotionListCtrl', [
|
.controller('MotionListCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'$state',
|
'$state',
|
||||||
@ -1144,11 +1224,12 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
.controller('MotionPollUpdateCtrl', [
|
.controller('MotionPollUpdateCtrl', [
|
||||||
'$scope',
|
'$scope',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
|
'Config',
|
||||||
'MotionPoll',
|
'MotionPoll',
|
||||||
'MotionPollForm',
|
'MotionPollForm',
|
||||||
'motionpoll',
|
'motionpoll',
|
||||||
'voteNumber',
|
'voteNumber',
|
||||||
function($scope, gettextCatalog, MotionPoll, MotionPollForm, motionpoll, voteNumber) {
|
function($scope, gettextCatalog, Config, MotionPoll, MotionPollForm, motionpoll, voteNumber) {
|
||||||
// set initial values for form model by create deep copy of motionpoll object
|
// set initial values for form model by create deep copy of motionpoll object
|
||||||
// so detail view is not updated while editing poll
|
// so detail view is not updated while editing poll
|
||||||
$scope.model = angular.copy(motionpoll);
|
$scope.model = angular.copy(motionpoll);
|
||||||
|
@ -163,7 +163,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<h3 ng-if="motion.polls.length > 0" translate>Voting result</h3>
|
<h3 ng-if="motion.polls.length > 0" translate>Voting result</h3>
|
||||||
<ol>
|
<ol>
|
||||||
<li ng-repeat="poll in motion.polls" class="spacer"
|
<li ng-controller="MotionPollDetailCtrl" ng-repeat="poll in motion.polls" class="spacer"
|
||||||
ng-if="poll.has_votes || operator.hasPerms('motions.can_manage')">
|
ng-if="poll.has_votes || operator.hasPerms('motions.can_manage')">
|
||||||
<strong translate-comment='ballot of a motion' translate>Vote</strong>
|
<strong translate-comment='ballot of a motion' translate>Vote</strong>
|
||||||
<!-- Edit poll -->
|
<!-- Edit poll -->
|
||||||
@ -185,7 +185,24 @@
|
|||||||
</a>
|
</a>
|
||||||
<!-- template hook for motion poll buttons -->
|
<!-- template hook for motion poll buttons -->
|
||||||
<template-hook hook-name="motionPollSmallButtons"></template-hook>
|
<template-hook hook-name="motionPollSmallButtons"></template-hook>
|
||||||
|
<!--setting for majority calculations-->
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="checkbox" ng-model="isMajorityCalculation">
|
||||||
|
<span translate> calculate majorities</span>
|
||||||
|
<a href="#" ng-click="isMajorityDetails = !isMajorityDetails">
|
||||||
|
<i class="fa toggle-icon" ng-class="isMajorityDetails ? 'fa-angle-up' : 'fa-angle-down'"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div uib-collapse="!isMajorityDetails" ng-cloak>
|
||||||
|
<div class="input-group">
|
||||||
|
<span translate> Minimal percentage needed:</span> <input type="number" min=0 max=100 step="any" ng-model="quorum"
|
||||||
|
ng-model-options="{debounce: 1000}" maxlength=9> %
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<span translate> Calculation base:</span>
|
||||||
|
<select ng-model="base" ng-options="option.value as option.display_name for option in basechoices"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Poll results -->
|
<!-- Poll results -->
|
||||||
<div ng-show="poll.has_votes" class="pollresults">
|
<div ng-show="poll.has_votes" class="pollresults">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@ -252,6 +269,19 @@
|
|||||||
<span class="result_value">
|
<span class="result_value">
|
||||||
{{ votesCast.value }} {{ votesCast.percentStr }}
|
{{ votesCast.value }} {{ votesCast.percentStr }}
|
||||||
</span>
|
</span>
|
||||||
|
<!-- majority calculation -->
|
||||||
|
<tr ng-if="isMajorityCalculation">
|
||||||
|
<td class="icon">
|
||||||
|
<td>
|
||||||
|
<span class="text-warning" ng-if="isPossible() === false" translate>Calculation impossible</span>
|
||||||
|
<span class="text-success" ng-if=" isReached() >= 0">
|
||||||
|
<translate>Quorum reached, </translate>
|
||||||
|
{{isReached()}} <translate>votes more than needed.</translate>
|
||||||
|
</span>
|
||||||
|
<span class="text-danger" ng-if="isReached() < 0">
|
||||||
|
<translate>Quorum not reached, </translate>
|
||||||
|
{{-(isReached())}} <translate> votes missing.</translate>
|
||||||
|
</span>
|
||||||
</table>
|
</table>
|
||||||
</ol>
|
</ol>
|
||||||
<button ng-if="motion.isAllowed('create_poll')" ng-click="create_poll()" class="btn btn-default btn-sm">
|
<button ng-if="motion.isAllowed('create_poll')" ng-click="create_poll()" class="btn btn-default btn-sm">
|
||||||
|
Loading…
Reference in New Issue
Block a user