182 lines
6.0 KiB
TypeScript
182 lines
6.0 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
|
|
import { ConfigService } from 'app/core/services/config.service';
|
|
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
|
import { PollService } from 'app/core/poll.service';
|
|
|
|
export type CalculablePollKey = 'votesvalid' | 'votesinvalid' | 'votescast' | 'yes' | 'no' | 'abstain';
|
|
|
|
/**
|
|
* Service class for motion polls.
|
|
*/
|
|
@Injectable({
|
|
providedIn: 'root'
|
|
})
|
|
export class MotionPollService extends PollService {
|
|
/**
|
|
* list of poll keys that are numbers and can be part of a quorum calculation
|
|
*/
|
|
public pollValues: CalculablePollKey[] = ['yes', 'no', 'abstain', 'votesvalid', 'votesinvalid', 'votescast'];
|
|
|
|
/**
|
|
* Constructor. Subscribes to the configuration values needed
|
|
* @param config ConfigService
|
|
*/
|
|
public constructor(config: ConfigService) {
|
|
super();
|
|
config.get('motions_poll_100_percent_base').subscribe(base => (this.percentBase = base));
|
|
config.get('motions_poll_default_majority_method').subscribe(method => (this.defaultMajorityMethod = method));
|
|
}
|
|
|
|
/**
|
|
* Calculates the percentage the given key reaches.
|
|
*
|
|
* @param poll
|
|
* @param key
|
|
* @returns a percentage number with two digits, null if the value cannot be calculated (consider 0 !== null)
|
|
*/
|
|
public calculatePercentage(poll: MotionPoll, key: CalculablePollKey): number {
|
|
const baseNumber = this.getBaseAmount(poll);
|
|
if (!baseNumber) {
|
|
return null;
|
|
}
|
|
switch (key) {
|
|
case 'abstain':
|
|
if (this.percentBase === 'YES_NO') {
|
|
return null;
|
|
}
|
|
break;
|
|
case 'votesinvalid':
|
|
if (this.percentBase !== 'CAST') {
|
|
return null;
|
|
}
|
|
break;
|
|
case 'votesvalid':
|
|
if (!['CAST', 'VALID'].includes(this.percentBase)) {
|
|
return null;
|
|
}
|
|
break;
|
|
case 'votescast':
|
|
if (this.percentBase !== 'CAST') {
|
|
return null;
|
|
}
|
|
}
|
|
return Math.round(((poll[key] * 100) / baseNumber) * 100) / 100;
|
|
}
|
|
|
|
/**
|
|
* Gets the number representing 100 percent for a given MotionPoll, depending
|
|
* on the configuration and the votes given.
|
|
*
|
|
* @param poll
|
|
* @returns the positive number representing 100 percent of the poll, 0 if
|
|
* the base cannot be calculated
|
|
*/
|
|
public getBaseAmount(poll: MotionPoll): number {
|
|
if (!poll) {
|
|
return 0;
|
|
}
|
|
switch (this.percentBase) {
|
|
case 'CAST':
|
|
if (!poll.votescast) {
|
|
return 0;
|
|
}
|
|
if (poll.votesinvalid < 0) {
|
|
return 0;
|
|
}
|
|
return poll.votescast;
|
|
case 'VALID':
|
|
if (poll.yes < 0 || poll.no < 0 || poll.abstain < 0) {
|
|
return 0;
|
|
}
|
|
return poll.votesvalid ? poll.votesvalid : 0;
|
|
case 'YES_NO_ABSTAIN':
|
|
if (poll.yes < 0 || poll.no < 0 || poll.abstain < 0) {
|
|
return 0;
|
|
}
|
|
return poll.yes + poll.no + poll.abstain;
|
|
case 'YES_NO':
|
|
if (poll.yes < 0 || poll.no < 0 || poll.abstain === -1) {
|
|
// It is not allowed to set 'Abstain' to 'majority' but exclude it from calculation.
|
|
// Setting 'Abstain' to 'undocumented' is possible, of course.
|
|
return 0;
|
|
}
|
|
return poll.yes + poll.no;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates which number is needed for the quorum to be surpassed
|
|
* TODO: Methods still hard coded to mirror the server's.
|
|
*
|
|
* @param poll
|
|
* @param method (optional) majority calculation method. If none is given,
|
|
* the default as set in the config will be used.
|
|
* @returns the first integer number larger than the required majority,
|
|
* undefined if a quorum cannot be calculated.
|
|
*/
|
|
public calculateQuorum(poll: MotionPoll, method?: string): number {
|
|
if (!method) {
|
|
method = this.defaultMajorityMethod;
|
|
}
|
|
const baseNumber = this.getBaseAmount(poll);
|
|
if (!baseNumber) {
|
|
return undefined;
|
|
}
|
|
let result: number;
|
|
switch (method) {
|
|
case 'simple_majority':
|
|
result = baseNumber * 0.5;
|
|
break;
|
|
case 'two-thirds_majority':
|
|
result = (baseNumber / 3) * 2;
|
|
break;
|
|
case 'three-quarters_majority':
|
|
result = (baseNumber / 4) * 3;
|
|
break;
|
|
default:
|
|
return undefined;
|
|
}
|
|
// rounding up, or if a integer was hit, adding one.
|
|
if (result % 1 !== 0) {
|
|
return Math.ceil(result);
|
|
} else {
|
|
return result + 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines if a value is abstract (percentages cannot be calculated)
|
|
*
|
|
* @param poll
|
|
* @param value
|
|
* @returns true if the percentages should not be calculated
|
|
*/
|
|
public isAbstractValue(poll: MotionPoll, value: CalculablePollKey): boolean {
|
|
if (this.getBaseAmount(poll) === 0) {
|
|
return true;
|
|
}
|
|
switch (this.percentBase) {
|
|
case 'YES_NO':
|
|
if (['votescast', 'votesinvalid', 'votesvalid', 'abstain'].includes(value)) {
|
|
return true;
|
|
}
|
|
break;
|
|
case 'YES_NO_ABSTAIN':
|
|
if (['votescast', 'votesinvalid', 'votesvalid'].includes(value)) {
|
|
return true;
|
|
}
|
|
break;
|
|
case 'VALID':
|
|
if (['votesinvalid', 'votescast'].includes(value)) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
if (poll[value] < 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|