diff --git a/CHANGELOG b/CHANGELOG index c8661eec5..e7bb8e99a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ Motions: - Added recommendations for motions. - Changed label of former state "commited a bill" to "refered to committee". - Added options to calculate percentages on different bases. +- Added majority calculation. Users: - Added field is_committee and new default group Committees. diff --git a/openslides/core/config.py b/openslides/core/config.py index 84ece613a..3ea38db27 100644 --- a/openslides/core/config.py +++ b/openslides/core/config.py @@ -12,7 +12,9 @@ INPUT_TYPE_MAPPING = { 'choice': str, 'comments': list, 'colorpicker': str, - 'datetimepicker': int} + 'datetimepicker': int, + 'majorityMethod': str, +} class ConfigHandler: diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index ea3c85529..20274ee22 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -5,6 +5,7 @@ // The core module for the OpenSlides site angular.module('OpenSlidesApp.core.site', [ 'OpenSlidesApp.core', + 'OpenSlidesApp.poll.majority', 'ui.router', 'angular-loading-bar', 'colorpicker.module', @@ -424,7 +425,7 @@ angular.module('OpenSlidesApp.core.site', [ 'Config', 'gettextCatalog', function($parse, Config, gettextCatalog) { - function getHtmlType(type) { + var getHtmlType = function (type) { return { string: 'text', text: 'textarea', @@ -434,8 +435,9 @@ angular.module('OpenSlidesApp.core.site', [ comments: 'comments', colorpicker: 'colorpicker', datetimepicker: 'datetimepicker', + majorityMethod: 'choice', }[type]; - } + }; return { restrict: 'E', @@ -585,11 +587,12 @@ angular.module('OpenSlidesApp.core.site', [ // Config Controller .controller('ConfigCtrl', [ '$scope', + 'MajorityMethodChoices', 'Config', 'configOptions', 'gettextCatalog', 'DateTimePickerTranslation', - function($scope, Config, configOptions, gettextCatalog, DateTimePickerTranslation) { + function($scope, MajorityMethodChoices, Config, configOptions, gettextCatalog, DateTimePickerTranslation) { Config.bindAll({}, $scope, 'configs'); $scope.configGroups = configOptions.data.config_groups; $scope.dateTimePickerTranslatedButtons = DateTimePickerTranslation.getButtons(); @@ -600,7 +603,7 @@ angular.module('OpenSlidesApp.core.site', [ Config.save(key); }; - /* For comments input */ + // For comments input $scope.addComment = function (key, parent) { parent.value.push({ name: gettextCatalog.getString('New'), @@ -612,6 +615,26 @@ angular.module('OpenSlidesApp.core.site', [ parent.value.splice(index, 1); $scope.save(key, parent.value); }; + + // For majority method + angular.forEach( + _.filter($scope.configGroups, function (configGroup) { + return configGroup.name === 'Motions' || configGroup.name === 'Elections'; + }), + function (configGroup) { + var configItem; + _.forEach(configGroup.subgroups, function (subgroup) { + configItem = _.find(subgroup.items, ['input_type', 'majorityMethod']); + if (configItem !== undefined) { + // Break the forEach loop if we found something. + return false; + } + }); + if (configItem !== undefined) { + configItem.choices = MajorityMethodChoices; + } + } + ); } ]) diff --git a/openslides/motions/config_variables.py b/openslides/motions/config_variables.py index d20931614..8c67dc4cf 100644 --- a/openslides/motions/config_variables.py +++ b/openslides/motions/config_variables.py @@ -186,6 +186,17 @@ def get_config_variables(): group='Motions', subgroup='Voting and ballot papers') + # TODO: Add server side validation of the choices. + yield ConfigVariable( + name='motions_poll_default_majority_method', + default_value='simple_majority', + input_type='majorityMethod', + label='Required majority', + help_text='Default method to check whether a motion has reached the required majority.', + weight=357, + group='Motions', + subgroup='Voting and ballot papers') + yield ConfigVariable( name='motions_pdf_ballot_papers_selection', default_value='CUSTOM_NUMBER', diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index a2c187a4c..902db5685 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -6,6 +6,7 @@ angular.module('OpenSlidesApp.motions.site', [ 'OpenSlidesApp.motions', 'OpenSlidesApp.motions.diff', 'OpenSlidesApp.motions.motionservices', + 'OpenSlidesApp.poll.majority', 'OpenSlidesApp.core.pdf', 'OpenSlidesApp.motions.pdf' ]) @@ -479,6 +480,54 @@ angular.module('OpenSlidesApp.motions.site', [ } ]) +// Cache for MotionPollDetailCtrl so that users choices are keeped during user actions (e. g. save poll form). +.value('MotionPollDetailCtrlCache', {}) + +// Child controller of MotionDetailCtrl for each single poll. +.controller('MotionPollDetailCtrl', [ + '$scope', + 'MajorityMethodChoices', + 'MotionMajority', + 'Config', + 'MotionPollDetailCtrlCache', + function ($scope, MajorityMethodChoices, MotionMajority, Config, MotionPollDetailCtrlCache) { + // Define choices. + $scope.methodChoices = MajorityMethodChoices; + // TODO: Get $scope.baseChoices from config_variables.py without copying them. + + // Setup empty cache with default values. + if (MotionPollDetailCtrlCache[$scope.poll.id] === undefined) { + MotionPollDetailCtrlCache[$scope.poll.id] = { + isMajorityCalculation: true, + isMajorityDetails: false, + method: $scope.config('motions_poll_default_majority_method'), + base: $scope.config('motions_poll_100_percent_base') + }; + } + + // Fetch users choices from cache. + $scope.isMajorityCalculation = MotionPollDetailCtrlCache[$scope.poll.id].isMajorityCalculation; + $scope.isMajorityDetails = MotionPollDetailCtrlCache[$scope.poll.id].isMajorityDetails; + $scope.method = MotionPollDetailCtrlCache[$scope.poll.id].method; + $scope.base = MotionPollDetailCtrlCache[$scope.poll.id].base; + + // Define result function. + $scope.isReached = function () { + return MotionMajority.isReached($scope.base, $scope.method, $scope.poll); + }; + + // Save current values to cache on detroy of this controller. + $scope.$on('$destroy', function() { + MotionPollDetailCtrlCache[$scope.poll.id] = { + isMajorityCalculation: $scope.isMajorityCalculation, + isMajorityDetails: $scope.isMajorityDetails, + method: $scope.method, + base: $scope.base + }; + }); + } +]) + .controller('MotionListCtrl', [ '$scope', '$state', @@ -1594,6 +1643,12 @@ angular.module('OpenSlidesApp.motions.site', [ gettext('All valid ballots'); gettext('All casted ballots'); gettext('Disabled (no percents)'); + gettext('Required majority'); + gettext('Default method to check whether a motion has reached the required majority.'); + gettext('Simple majority'); + gettext('Two-thirds majority'); + gettext('Three-quarters majority'); + gettext('Disabled'); gettext('Number of ballot papers (selection)'); gettext('Number of all delegates'); gettext('Number of all participants'); diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index 5c2650bdb..337b06c32 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -163,14 +163,16 @@

Voting result

    -
  1. Vote + + - - + - + + + +
    +
    + Required majority: +