Merge pull request #4594 from FinnStutzenstein/modelStructureForAssignments
Background structure for assignments
This commit is contained in:
commit
259afa7f88
@ -87,6 +87,7 @@ matrix:
|
||||
install:
|
||||
- npm install
|
||||
script:
|
||||
- npm list --depth=0 || cat --help
|
||||
- npm run prettify-check
|
||||
|
||||
- language: node_js
|
||||
|
@ -79,7 +79,7 @@
|
||||
"karma-jasmine": "~2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^1.16.4",
|
||||
"prettier": "^1.17.0",
|
||||
"protractor": "^5.4.2",
|
||||
"source-map-explorer": "^1.7.0",
|
||||
"terser": "3.16.1",
|
||||
|
@ -3,14 +3,14 @@ import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { AssignmentUser } from 'app/shared/models/assignments/assignment-user';
|
||||
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
|
||||
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
|
||||
import { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { DataStoreService } from '../../core-services/data-store.service';
|
||||
import { HttpService } from 'app/core/core-services/http.service';
|
||||
import { Item } from 'app/shared/models/agenda/item';
|
||||
import { Poll } from 'app/shared/models/assignments/poll';
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { Tag } from 'app/shared/models/core/tag';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||
@ -18,6 +18,9 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewAssignmentRelatedUser } from 'app/site/assignments/models/view-assignment-related-user';
|
||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||
import { ViewAssignmentPollOption } from 'app/site/assignments/models/view-assignment-poll-option';
|
||||
|
||||
/**
|
||||
* Repository Service for Assignments.
|
||||
@ -69,17 +72,43 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
||||
};
|
||||
|
||||
public createViewModel(assignment: Assignment): ViewAssignment {
|
||||
const relatedUser = this.viewModelStoreService.getMany(ViewUser, assignment.candidates_id);
|
||||
const agendaItem = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id);
|
||||
const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id);
|
||||
const assignmentRelatedUsers = this.createViewAssignmentRelatedUsers(assignment.assignment_related_users);
|
||||
const assignmentPolls = this.createViewAssignmentPolls(assignment.polls);
|
||||
|
||||
const viewAssignment = new ViewAssignment(assignment, relatedUser, agendaItem, tags);
|
||||
const viewAssignment = new ViewAssignment(
|
||||
assignment,
|
||||
assignmentRelatedUsers,
|
||||
assignmentPolls,
|
||||
agendaItem,
|
||||
tags
|
||||
);
|
||||
viewAssignment.getVerboseName = this.getVerboseName;
|
||||
viewAssignment.getAgendaTitle = () => this.getAgendaTitle(viewAssignment);
|
||||
viewAssignment.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewAssignment);
|
||||
return viewAssignment;
|
||||
}
|
||||
|
||||
private createViewAssignmentRelatedUsers(
|
||||
assignmentRelatedUsers: AssignmentRelatedUser[]
|
||||
): ViewAssignmentRelatedUser[] {
|
||||
return assignmentRelatedUsers.map(aru => {
|
||||
const user = this.viewModelStoreService.get(ViewUser, aru.user_id);
|
||||
return new ViewAssignmentRelatedUser(aru, user);
|
||||
});
|
||||
}
|
||||
|
||||
private createViewAssignmentPolls(assignmentPolls: AssignmentPoll[]): ViewAssignmentPoll[] {
|
||||
return assignmentPolls.map(poll => {
|
||||
const options = poll.options.map(option => {
|
||||
const user = this.viewModelStoreService.get(ViewUser, option.candidate_id);
|
||||
return new ViewAssignmentPollOption(option, user);
|
||||
});
|
||||
return new ViewAssignmentPoll(poll, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another user as a candidate
|
||||
*
|
||||
@ -127,7 +156,7 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
||||
*
|
||||
* @param id id of the poll to delete
|
||||
*/
|
||||
public async deletePoll(poll: Poll): Promise<void> {
|
||||
public async deletePoll(poll: ViewAssignmentPoll): Promise<void> {
|
||||
await this.httpService.delete(`${this.restPollPath}${poll.id}/`);
|
||||
}
|
||||
|
||||
@ -139,8 +168,8 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
||||
*
|
||||
* TODO: check if votes is untouched
|
||||
*/
|
||||
public async updatePoll(poll: Partial<Poll>, originalPoll: Poll): Promise<void> {
|
||||
const data: Poll = Object.assign(originalPoll, poll);
|
||||
public async updatePoll(poll: Partial<AssignmentPoll>, originalPoll: ViewAssignmentPoll): Promise<void> {
|
||||
const data: AssignmentPoll = Object.assign(originalPoll.poll, poll);
|
||||
await this.httpService.patch(`${this.restPollPath}${originalPoll.id}/`, data);
|
||||
}
|
||||
|
||||
@ -151,7 +180,7 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
||||
* @param poll the updated Poll
|
||||
* @param originalPoll the original poll
|
||||
*/
|
||||
public async updateVotes(poll: Partial<Poll>, originalPoll: Poll): Promise<void> {
|
||||
public async updateVotes(poll: Partial<AssignmentPoll>, originalPoll: ViewAssignmentPoll): Promise<void> {
|
||||
poll.options.sort((a, b) => a.weight - b.weight);
|
||||
const votes = poll.options.map(option => {
|
||||
switch (poll.pollmethod) {
|
||||
@ -185,12 +214,16 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
|
||||
/**
|
||||
* change the 'elected' state of an election candidate
|
||||
*
|
||||
* @param user
|
||||
* @param assignmentRelatedUser
|
||||
* @param assignment
|
||||
* @param elected true if the candidate is to be elected, false if unelected
|
||||
*/
|
||||
public async markElected(user: AssignmentUser, assignment: ViewAssignment, elected: boolean): Promise<void> {
|
||||
const data = { user: user.user_id };
|
||||
public async markElected(
|
||||
assignmentRelatedUser: ViewAssignmentRelatedUser,
|
||||
assignment: ViewAssignment,
|
||||
elected: boolean
|
||||
): Promise<void> {
|
||||
const data = { user: assignmentRelatedUser.user_id };
|
||||
if (elected) {
|
||||
await this.httpService.post(this.restPath + assignment.id + this.markElectedPath, data);
|
||||
} else {
|
||||
|
@ -7,12 +7,12 @@ import { PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||
* part of the 'polls-options'-array in poll
|
||||
* @ignore
|
||||
*/
|
||||
export class PollOption extends Deserializer {
|
||||
export class AssignmentPollOption extends Deserializer {
|
||||
public id: number; // The AssignmentUser id of the candidate
|
||||
public candidate_id: number; // the User id of the candidate
|
||||
public is_elected: boolean;
|
||||
public votes: {
|
||||
weight: number; // TODO arrives as string?
|
||||
weight: number; // represented as a string because it's a decimal field
|
||||
value: PollVoteValue;
|
||||
}[];
|
||||
public poll_id: number;
|
||||
@ -24,21 +24,12 @@ export class PollOption extends Deserializer {
|
||||
* @param input
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
// cast stringify numbers
|
||||
if (typeof input === 'object') {
|
||||
Object.keys(input).forEach(key => {
|
||||
if (typeof input[key] === 'string') {
|
||||
input[key] = parseInt(input[key], 10);
|
||||
if (input && input.votes) {
|
||||
input.votes.forEach(vote => {
|
||||
if (vote.weight) {
|
||||
vote.weight = parseFloat(vote.weight);
|
||||
}
|
||||
});
|
||||
if (input.votes) {
|
||||
input.votes = input.votes.map(vote => {
|
||||
return {
|
||||
value: vote.value,
|
||||
weight: parseInt(vote.weight, 10)
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
super(input);
|
||||
}
|
@ -1,44 +1,37 @@
|
||||
import { AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service';
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
import { PollOption } from './poll-option';
|
||||
import { AssignmentPollOption } from './assignment-poll-option';
|
||||
|
||||
/**
|
||||
* Content of the 'polls' property of assignments
|
||||
* @ignore
|
||||
*/
|
||||
export class Poll extends Deserializer {
|
||||
export class AssignmentPoll extends Deserializer {
|
||||
private static DECIMAL_FIELDS = ['votesvalid', 'votesinvalid', 'votescast'];
|
||||
|
||||
public id: number;
|
||||
public pollmethod: AssignmentPollMethod;
|
||||
public description: string;
|
||||
public published: boolean;
|
||||
public options: PollOption[];
|
||||
public options: AssignmentPollOption[];
|
||||
public votesvalid: number;
|
||||
public votesinvalid: number;
|
||||
public votescast: number;
|
||||
public has_votes: boolean;
|
||||
public assignment_id: number;
|
||||
|
||||
/**
|
||||
* (temporary?) storing the base values for percentage calculations,
|
||||
* to avoid recalculating pollBases too often
|
||||
* (the calculation iterates through all pollOptions in some use cases)
|
||||
*/
|
||||
public pollBase: number;
|
||||
|
||||
/**
|
||||
* Needs to be completely optional because assignment has (yet) the optional parameter 'polls'
|
||||
* @param input
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
// cast stringify numbers
|
||||
if (typeof input === 'object') {
|
||||
const numberifyKeys = ['id', 'votesvalid', 'votesinvalid', 'votescast', 'assignment_id'];
|
||||
|
||||
for (const key of Object.keys(input)) {
|
||||
if (numberifyKeys.includes(key) && typeof input[key] === 'string') {
|
||||
input[key] = parseInt(input[key], 10);
|
||||
}
|
||||
if (input) {
|
||||
AssignmentPoll.DECIMAL_FIELDS.forEach(field => {
|
||||
if (input[field] && typeof input[field] === 'string') {
|
||||
input[field] = parseFloat(input[field]);
|
||||
}
|
||||
});
|
||||
}
|
||||
super(input);
|
||||
}
|
||||
@ -47,7 +40,7 @@ export class Poll extends Deserializer {
|
||||
Object.assign(this, input);
|
||||
this.options = [];
|
||||
if (input.options instanceof Array) {
|
||||
this.options = input.options.map(pollOptionData => new PollOption(pollOptionData));
|
||||
this.options = input.options.map(pollOptionData => new AssignmentPollOption(pollOptionData));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Content of the 'assignment_related_users' property.
|
||||
*/
|
||||
export interface AssignmentRelatedUser {
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* id of the user this assignment user relates to
|
||||
*/
|
||||
user_id: number;
|
||||
|
||||
/**
|
||||
* The current 'elected' state
|
||||
*/
|
||||
elected: boolean;
|
||||
|
||||
/**
|
||||
* id of the related assignment
|
||||
*/
|
||||
assignment_id: number;
|
||||
|
||||
/**
|
||||
* A weight to determine the position in the list of candidates
|
||||
* (determined by the server)
|
||||
*/
|
||||
weight: number;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
|
||||
/**
|
||||
* Content of the 'assignment_related_users' property.
|
||||
* Note that this differs from a ViewUser (e.g. different id)
|
||||
* @ignore
|
||||
*/
|
||||
export class AssignmentUser extends Deserializer {
|
||||
public id: number;
|
||||
|
||||
/**
|
||||
* id of the user this assignment user relates to
|
||||
*/
|
||||
public user_id: number;
|
||||
|
||||
/**
|
||||
* The current 'elected' state
|
||||
*/
|
||||
public elected: boolean;
|
||||
|
||||
/**
|
||||
* id of the related assignment
|
||||
*/
|
||||
public assignment_id: number;
|
||||
|
||||
/**
|
||||
* A weight to determine the position in the list of candidates
|
||||
* (determined by the server)
|
||||
*/
|
||||
public weight: number;
|
||||
|
||||
/**
|
||||
* Constructor. Needs to be completely optional because assignment has
|
||||
* (yet) the optional parameter 'assignment_related_users'
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
super(input);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { AssignmentUser } from './assignment-user';
|
||||
import { Poll } from './poll';
|
||||
import { BaseModel } from '../base/base-model';
|
||||
import { AssignmentRelatedUser } from './assignment-related-user';
|
||||
import { AssignmentPoll } from './assignment-poll';
|
||||
|
||||
/**
|
||||
* Representation of an assignment.
|
||||
@ -8,14 +8,15 @@ import { BaseModel } from '../base/base-model';
|
||||
*/
|
||||
export class Assignment extends BaseModel<Assignment> {
|
||||
public static COLLECTIONSTRING = 'assignments/assignment';
|
||||
|
||||
public id: number;
|
||||
public title: string;
|
||||
public description: string;
|
||||
public open_posts: number;
|
||||
public phase: number; // see Openslides constants
|
||||
public assignment_related_users: AssignmentUser[];
|
||||
public assignment_related_users: AssignmentRelatedUser[];
|
||||
public poll_description_default: number;
|
||||
public polls: Poll[];
|
||||
public polls: AssignmentPoll[];
|
||||
public agenda_item_id: number;
|
||||
public tags_id: number[];
|
||||
|
||||
@ -25,25 +26,18 @@ export class Assignment extends BaseModel<Assignment> {
|
||||
|
||||
public get candidates_id(): number[] {
|
||||
return this.assignment_related_users
|
||||
.sort((a: AssignmentUser, b: AssignmentUser) => {
|
||||
.sort((a: AssignmentRelatedUser, b: AssignmentRelatedUser) => {
|
||||
return a.weight - b.weight;
|
||||
})
|
||||
.map((candidate: AssignmentUser) => candidate.user_id);
|
||||
.map((candidate: AssignmentRelatedUser) => candidate.user_id);
|
||||
}
|
||||
|
||||
public deserialize(input: any): void {
|
||||
Object.assign(this, input);
|
||||
|
||||
this.assignment_related_users = [];
|
||||
if (input.assignment_related_users instanceof Array) {
|
||||
this.assignment_related_users = input.assignment_related_users.map(
|
||||
assignmentUserData => new AssignmentUser(assignmentUserData)
|
||||
);
|
||||
}
|
||||
|
||||
this.polls = [];
|
||||
if (input.polls instanceof Array) {
|
||||
this.polls = input.polls.map(pollData => new Poll(pollData));
|
||||
this.polls = input.polls.map(pollData => new AssignmentPoll(pollData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
client/src/app/shared/pipes/precision.pipe.ts
Normal file
31
client/src/app/shared/pipes/precision.pipe.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
|
||||
/**
|
||||
* Formats floats to have a defined precision.
|
||||
*
|
||||
* In this default application, the precision is 0, so no decimal places. Plugins
|
||||
* may override this pipe to enable more precise votes.
|
||||
*/
|
||||
@Pipe({
|
||||
name: 'precisionPipe'
|
||||
})
|
||||
export class PrecisionPipe implements PipeTransform {
|
||||
protected precision: number;
|
||||
|
||||
public constructor(private decimalPipe: DecimalPipe) {
|
||||
this.precision = 0;
|
||||
}
|
||||
|
||||
public transform(value: number, precision?: number): string {
|
||||
if (!precision) {
|
||||
precision = this.precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Min 1 place before the comma and exactly precision places after.
|
||||
*/
|
||||
const digitsInfo = `1.${precision}-${precision}`;
|
||||
return this.decimalPipe.transform(value, digitsInfo);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CommonModule, DecimalPipe } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { OwlDateTimeModule, OwlNativeDateTimeModule } from 'ng-pick-datetime';
|
||||
|
||||
@ -81,6 +81,7 @@ import { ProjectorComponent } from './components/projector/projector.component';
|
||||
import { SlideContainerComponent } from './components/slide-container/slide-container.component';
|
||||
import { CountdownTimeComponent } from './components/contdown-time/countdown-time.component';
|
||||
import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
|
||||
import { PrecisionPipe } from './pipes/precision.pipe';
|
||||
|
||||
/**
|
||||
* Share Module for all "dumb" components and pipes.
|
||||
@ -201,7 +202,8 @@ import { MediaUploadContentComponent } from './components/media-upload-content/m
|
||||
OwlDateTimeModule,
|
||||
OwlNativeDateTimeModule,
|
||||
CountdownTimeComponent,
|
||||
MediaUploadContentComponent
|
||||
MediaUploadContentComponent,
|
||||
PrecisionPipe
|
||||
],
|
||||
declarations: [
|
||||
PermsDirective,
|
||||
@ -228,7 +230,8 @@ import { MediaUploadContentComponent } from './components/media-upload-content/m
|
||||
ProjectorComponent,
|
||||
SlideContainerComponent,
|
||||
CountdownTimeComponent,
|
||||
MediaUploadContentComponent
|
||||
MediaUploadContentComponent,
|
||||
PrecisionPipe
|
||||
],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||
@ -236,7 +239,8 @@ import { MediaUploadContentComponent } from './components/media-upload-content/m
|
||||
SortingListComponent,
|
||||
SortingTreeComponent,
|
||||
OsSortFilterBarComponent,
|
||||
OsSortBottomSheetComponent
|
||||
OsSortBottomSheetComponent,
|
||||
DecimalPipe
|
||||
],
|
||||
entryComponents: [OsSortBottomSheetComponent, C4DialogComponent]
|
||||
})
|
||||
|
@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { AssignmentDetailComponent } from './components/assignment-detail/assignment-detail.component';
|
||||
import { AssignmentListComponent } from './assignment-list/assignment-list.component';
|
||||
import { AssignmentListComponent } from './components/assignment-list/assignment-list.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: AssignmentListComponent, pathMatch: 'full' },
|
||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { AssignmentDetailComponent } from './components/assignment-detail/assignment-detail.component';
|
||||
import { AssignmentListComponent } from './assignment-list/assignment-list.component';
|
||||
import { AssignmentListComponent } from './components/assignment-list/assignment-list.component';
|
||||
import { AssignmentPollComponent } from './components/assignment-poll/assignment-poll.component';
|
||||
import { AssignmentPollDialogComponent } from './components/assignment-poll/assignment-poll-dialog.component';
|
||||
import { AssignmentPollDialogComponent } from './components/assignment-poll-dialog/assignment-poll-dialog.component';
|
||||
import { AssignmentsRoutingModule } from './assignments-routing.module';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
|
@ -9,8 +9,7 @@
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="assignment && !newAssignment">
|
||||
<span translate>Election</span>
|
||||
<span> </span> <span *ngIf="!editAssignment">{{ assignment.title }}</span>
|
||||
<span *ngIf="!editAssignment">{{ assignment.getTitle() }}</span>
|
||||
<span *ngIf="editAssignment">{{ assignmentForm.get('title').value }}</span>
|
||||
</h2>
|
||||
<h2 *ngIf="newAssignment" translate>New election</h2>
|
||||
@ -47,7 +46,7 @@
|
||||
<!-- Title -->
|
||||
<div class="title on-transition-fade" *ngIf="assignment && !editAssignment">
|
||||
<div class="title-line">
|
||||
<h1>{{ assignment.title }}</h1>
|
||||
<h1>{{ assignment.getTitle() }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<ng-container *ngIf="vp.isMobile; then mobileView; else desktopView"></ng-container>
|
||||
@ -131,7 +130,7 @@
|
||||
<div *ngIf="assignment && assignment.candidates">
|
||||
<!-- TODO: Sorting -->
|
||||
<div *ngFor="let candidate of assignment.candidates">
|
||||
<span>{{ candidate.username }}</span>
|
||||
<span>{{ candidate.full_name }}</span>
|
||||
<button mat-button *ngIf="hasPerms('addOthers')" (click)="removeUser(candidate)">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { MatSnackBar, MatSelectChange } from '@angular/material';
|
||||
@ -6,6 +5,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||
@ -15,7 +15,7 @@ import { ConstantsService } from 'app/core/ui-services/constants.service';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { Poll } from 'app/shared/models/assignments/poll';
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||
import { ViewAssignment, AssignmentPhase } from '../../models/view-assignment';
|
||||
@ -23,6 +23,7 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
|
||||
/**
|
||||
* Component for the assignment detail view
|
||||
@ -87,7 +88,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
*/
|
||||
public set assignment(assignment: ViewAssignment) {
|
||||
this._assignment = assignment;
|
||||
if (this.assignment.polls && this.assignment.polls.length) {
|
||||
if (this.assignment.polls.length) {
|
||||
this.assignment.polls.forEach(poll => {
|
||||
poll.pollBase = this.pollService.getBaseAmount(poll);
|
||||
});
|
||||
@ -148,6 +149,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* @param pollService
|
||||
* @param agendaRepo
|
||||
* @param tagRepo
|
||||
* @param promptService
|
||||
*/
|
||||
public constructor(
|
||||
title: Title,
|
||||
@ -164,7 +166,8 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
private constants: ConstantsService,
|
||||
public pollService: AssignmentPollService,
|
||||
private agendaRepo: ItemRepositoryService,
|
||||
private tagRepo: TagRepositoryService
|
||||
private tagRepo: TagRepositoryService,
|
||||
private promptService: PromptService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
/* Server side constants for phases */
|
||||
@ -342,7 +345,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
} else {
|
||||
this.newAssignment = true;
|
||||
// TODO set defaults?
|
||||
this.assignment = new ViewAssignment(new Assignment());
|
||||
this.assignment = new ViewAssignment(new Assignment(), [], []);
|
||||
this.patchForm(this.assignment);
|
||||
this.setEditMode(true);
|
||||
}
|
||||
@ -350,9 +353,14 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
|
||||
/**
|
||||
* Handler for deleting the assignment
|
||||
* TODO: navigating to assignment overview on delete
|
||||
*/
|
||||
public onDeleteAssignmentButton(): void {}
|
||||
public async onDeleteAssignmentButton(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this election?');
|
||||
if (await this.promptService.open(title, this.assignment.getTitle())) {
|
||||
await this.repo.delete(this.assignment);
|
||||
this.router.navigate(['../assignments/']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for actions to be done on change of displayed poll
|
||||
@ -420,7 +428,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* Assemble a meaningful label for the poll
|
||||
* TODO (currently e.g. 'Ballot 10 (unublished)')
|
||||
*/
|
||||
public getPollLabel(poll: Poll, index: number): string {
|
||||
public getPollLabel(poll: AssignmentPoll, index: number): string {
|
||||
const pubState = poll.published ? this.translate.instant('published') : this.translate.instant('unpublished');
|
||||
const title = this.translate.instant('Ballot');
|
||||
return `${title} ${index + 1} (${pubState})`;
|
||||
|
@ -38,7 +38,7 @@
|
||||
<!-- name column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
|
||||
<mat-cell *matCellDef="let assignment">{{ assignment.getTitle() }}</mat-cell>
|
||||
<mat-cell *matCellDef="let assignment">{{ assignment.getListTitle() }}</mat-cell>
|
||||
</ng-container>
|
||||
<!-- pahse column-->
|
||||
<ng-container matColumnDef="phase">
|
@ -1,7 +1,7 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AssignmentListComponent } from './assignment-list.component';
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
import { E2EImportsModule } from '../../../../../e2e-imports.module';
|
||||
|
||||
describe('AssignmentListComponent', () => {
|
||||
let component: AssignmentListComponent;
|
@ -1,18 +1,19 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { AssignmentFilterListService } from '../services/assignment-filter.service';
|
||||
import { AssignmentSortListService } from '../services/assignment-sort-list.service';
|
||||
import { AssignmentFilterListService } from '../../services/assignment-filter.service';
|
||||
import { AssignmentSortListService } from '../../services/assignment-sort-list.service';
|
||||
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
||||
import { ListViewBaseComponent } from '../../base/list-view-base';
|
||||
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { StorageService } from 'app/core/core-services/storage.service';
|
||||
import { ViewAssignment } from '../models/view-assignment';
|
||||
import { ViewAssignment } from '../../models/view-assignment';
|
||||
|
||||
/**
|
||||
* List view for the assignments
|
||||
@ -97,7 +98,7 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete all selected elections?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title, '')) {
|
||||
for (const assignment of this.selectedRows) {
|
||||
await this.repo.delete(assignment);
|
||||
}
|
@ -4,9 +4,9 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||
import { CalculablePollKey, PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||
import { Poll } from 'app/shared/models/assignments/poll';
|
||||
import { PollOption } from 'app/shared/models/assignments/poll-option';
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
||||
|
||||
/**
|
||||
* Vote entries included once for summary (e.g. total votes cast)
|
||||
@ -40,7 +40,7 @@ export class AssignmentPollDialogComponent {
|
||||
*/
|
||||
public constructor(
|
||||
public dialogRef: MatDialogRef<AssignmentPollDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: { poll: Poll; users: ViewUser[] },
|
||||
@Inject(MAT_DIALOG_DATA) public data: { poll: AssignmentPoll; users: ViewUser[] },
|
||||
private matSnackBar: MatSnackBar,
|
||||
private translate: TranslateService,
|
||||
private pollService: AssignmentPollService
|
||||
@ -123,7 +123,7 @@ export class AssignmentPollDialogComponent {
|
||||
* @param candidate the candidate for whom to update the value
|
||||
* @param newData the new value
|
||||
*/
|
||||
public setValue(value: PollVoteValue, candidate: PollOption, newData: string): void {
|
||||
public setValue(value: PollVoteValue, candidate: AssignmentPollOption, newData: string): void {
|
||||
const vote = candidate.votes.find(v => v.value === value);
|
||||
if (vote) {
|
||||
vote.weight = parseInt(newData, 10);
|
||||
@ -142,7 +142,7 @@ export class AssignmentPollDialogComponent {
|
||||
* @param candidate the pollOption
|
||||
* @returns the currently entered number or undefined if no number has been set
|
||||
*/
|
||||
public getValue(value: PollVoteValue, candidate: PollOption): number | undefined {
|
||||
public getValue(value: PollVoteValue, candidate: AssignmentPollOption): number | undefined {
|
||||
const val = candidate.votes.find(v => v.value === value);
|
||||
return val ? val.weight : undefined;
|
||||
}
|
@ -64,7 +64,7 @@
|
||||
</div>
|
||||
<!-- candidate Name -->
|
||||
<div>
|
||||
{{ getCandidateName(option) }}
|
||||
{{ option.user.full_name }}
|
||||
</div>
|
||||
<!-- Votes -->
|
||||
<div *ngIf="poll.published && poll.has_votes">
|
||||
@ -111,6 +111,7 @@
|
||||
<span>{{ key | translate }}</span>:
|
||||
</div>
|
||||
<div>
|
||||
{{ poll[key] | precisionPipe }}
|
||||
{{ pollService.getSpecialLabel(poll[key]) }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,17 +3,18 @@ import { MatDialog, MatSnackBar } from '@angular/material';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { AssignmentPollDialogComponent } from './assignment-poll-dialog.component';
|
||||
import { AssignmentPollDialogComponent } from '../assignment-poll-dialog/assignment-poll-dialog.component';
|
||||
import { AssignmentPollService } from '../../services/assignment-poll.service';
|
||||
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
|
||||
import { MajorityMethod, CalculablePollKey } from 'app/core/ui-services/poll.service';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { Poll } from 'app/shared/models/assignments/poll';
|
||||
import { PollOption } from 'app/shared/models/assignments/poll-option';
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ViewAssignment } from '../../models/view-assignment';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ViewAssignmentPollOption } from '../../models/view-assignment-poll-option';
|
||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||
|
||||
/**
|
||||
* Component for a single assignment poll. Used in assignment detail view
|
||||
@ -34,7 +35,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
* The poll represented in this component
|
||||
*/
|
||||
@Input()
|
||||
public poll: Poll;
|
||||
public poll: ViewAssignmentPoll;
|
||||
|
||||
/**
|
||||
* The selected Majority method to display quorum calculations. Will be
|
||||
@ -135,25 +136,10 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
*
|
||||
* TODO Print the ballots for this poll.
|
||||
*/
|
||||
public printBallot(poll: Poll): void {
|
||||
public printBallot(poll: AssignmentPoll): void {
|
||||
this.raiseError('Not yet implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the name for a candidate from the assignment
|
||||
*
|
||||
* @param option Any poll option
|
||||
* @returns the full_name for the candidate
|
||||
*/
|
||||
public getCandidateName(option: PollOption): string {
|
||||
const user = this.assignment.candidates.find(candidate => candidate.id === option.candidate_id);
|
||||
return user ? user.full_name : '';
|
||||
// TODO this.assignment.candidates may not contain every candidates' name (if deleted later)
|
||||
// so we should rather use this.userRepo.getViewModel(option.id).full_name
|
||||
// TODO is this name always available?
|
||||
// TODO error handling
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the candidate has reached the majority needed to pass
|
||||
* the quorum
|
||||
@ -161,7 +147,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
* @param option
|
||||
* @returns true if the quorum is successfully met
|
||||
*/
|
||||
public quorumReached(option: PollOption): boolean {
|
||||
public quorumReached(option: ViewAssignmentPollOption): boolean {
|
||||
const yesValue = this.poll.pollmethod === 'votes' ? 'Votes' : 'Yes';
|
||||
const amount = option.votes.find(v => v.value === yesValue).weight;
|
||||
const yesQuorum = this.pollService.yesQuorum(this.majorityChoice, this.poll, option);
|
||||
@ -214,17 +200,17 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
*
|
||||
* @param option
|
||||
*/
|
||||
public toggleElected(option: PollOption): void {
|
||||
public toggleElected(option: ViewAssignmentPollOption): void {
|
||||
if (!this.operator.hasPerms('assignments.can_manage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO additional conditions: assignment not finished?
|
||||
const candidate = this.assignment.assignment.assignment_related_users.find(
|
||||
user => user.user_id === option.candidate_id
|
||||
const viewAssignmentRelatedUser = this.assignment.assignmentRelatedUsers.find(
|
||||
user => user.user_id === option.user_id
|
||||
);
|
||||
if (candidate) {
|
||||
this.assignmentRepo.markElected(candidate, this.assignment, !option.is_elected);
|
||||
if (viewAssignmentRelatedUser) {
|
||||
this.assignmentRepo.markElected(viewAssignmentRelatedUser, this.assignment, !option.is_elected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
||||
|
||||
export class ViewAssignmentPollOption implements Identifiable, Updateable {
|
||||
private _assignmentPollOption: AssignmentPollOption;
|
||||
private _user: ViewUser; // This is the "candidate". We'll stay consistent wich user here...
|
||||
|
||||
public get option(): AssignmentPollOption {
|
||||
return this._assignmentPollOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: "User" instead of "candidate" to be consistent.
|
||||
*/
|
||||
public get user(): ViewUser | null {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.option.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: "User" instead of "candidate" to be consistent.
|
||||
*/
|
||||
public get user_id(): number {
|
||||
return this.option.candidate_id;
|
||||
}
|
||||
|
||||
public get is_elected(): boolean {
|
||||
return this.option.is_elected;
|
||||
}
|
||||
|
||||
public get votes(): {
|
||||
weight: number;
|
||||
value: PollVoteValue;
|
||||
}[] {
|
||||
return this.option.votes;
|
||||
}
|
||||
|
||||
public get poll_id(): number {
|
||||
return this.option.poll_id;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.option.weight;
|
||||
}
|
||||
|
||||
public constructor(assignmentPollOption: AssignmentPollOption, user?: ViewUser) {
|
||||
this._assignmentPollOption = assignmentPollOption;
|
||||
this._user = user;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewUser && update.id === this.user_id) {
|
||||
this._user = update;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { AssignmentPollMethod } from '../services/assignment-poll.service';
|
||||
import { ViewAssignmentPollOption } from './view-assignment-poll-option';
|
||||
|
||||
export class ViewAssignmentPoll implements Identifiable, Updateable {
|
||||
private _assignmentPoll: AssignmentPoll;
|
||||
private _assignmentPollOptions: ViewAssignmentPollOption[];
|
||||
|
||||
public get poll(): AssignmentPoll {
|
||||
return this._assignmentPoll;
|
||||
}
|
||||
|
||||
public get options(): ViewAssignmentPollOption[] {
|
||||
return this._assignmentPollOptions;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.poll.id;
|
||||
}
|
||||
|
||||
public get pollmethod(): AssignmentPollMethod {
|
||||
return this.poll.pollmethod;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this.poll.description;
|
||||
}
|
||||
|
||||
public get published(): boolean {
|
||||
return this.poll.published;
|
||||
}
|
||||
|
||||
public get votesvalid(): number {
|
||||
return this.poll.votesvalid;
|
||||
}
|
||||
|
||||
public get votesinvalid(): number {
|
||||
return this.poll.votesinvalid;
|
||||
}
|
||||
|
||||
public get votescast(): number {
|
||||
return this.poll.votescast;
|
||||
}
|
||||
|
||||
public get has_votes(): boolean {
|
||||
return this.poll.has_votes;
|
||||
}
|
||||
|
||||
public get assignment_id(): number {
|
||||
return this.poll.assignment_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* storing the base values for percentage calculations,
|
||||
* to avoid recalculating pollBases too often
|
||||
* (the calculation iterates through all pollOptions in some use cases)
|
||||
*/
|
||||
public pollBase: number;
|
||||
|
||||
public constructor(assignmentPoll: AssignmentPoll, assignmentPollOptions: ViewAssignmentPollOption[]) {
|
||||
this._assignmentPoll = assignmentPoll;
|
||||
this._assignmentPollOptions = assignmentPollOptions;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
this.options.forEach(option => option.updateDependencies(update));
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
|
||||
export class ViewAssignmentRelatedUser implements Updateable, Identifiable {
|
||||
private _assignmentRelatedUser: AssignmentRelatedUser;
|
||||
private _user?: ViewUser;
|
||||
|
||||
public get assignmentRelatedUser(): AssignmentRelatedUser {
|
||||
return this._assignmentRelatedUser;
|
||||
}
|
||||
|
||||
public get user(): ViewUser {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.assignmentRelatedUser.id;
|
||||
}
|
||||
|
||||
public get user_id(): number {
|
||||
return this.assignmentRelatedUser.user_id;
|
||||
}
|
||||
|
||||
public get assignment_id(): number {
|
||||
return this.assignmentRelatedUser.assignment_id;
|
||||
}
|
||||
|
||||
public get elected(): boolean {
|
||||
return this.assignmentRelatedUser.elected;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.assignmentRelatedUser.weight;
|
||||
}
|
||||
|
||||
public constructor(assignmentRelatedUser: AssignmentRelatedUser, user?: ViewUser) {
|
||||
this._assignmentRelatedUser = assignmentRelatedUser;
|
||||
this._user = user;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewUser && update.id === this.user_id) {
|
||||
this._user = update;
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,8 @@ import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Poll } from 'app/shared/models/assignments/poll';
|
||||
import { ViewAssignmentRelatedUser } from './view-assignment-related-user';
|
||||
import { ViewAssignmentPoll } from './view-assignment-poll';
|
||||
|
||||
export interface AssignmentPhase {
|
||||
value: number;
|
||||
@ -17,9 +18,10 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING;
|
||||
|
||||
private _assignment: Assignment;
|
||||
private _relatedUser: ViewUser[];
|
||||
private _agendaItem: ViewItem;
|
||||
private _tags: ViewTag[];
|
||||
private _assignmentRelatedUsers: ViewAssignmentRelatedUser[];
|
||||
private _assignmentPolls: ViewAssignmentPoll[];
|
||||
private _agendaItem?: ViewItem;
|
||||
private _tags?: ViewTag[];
|
||||
|
||||
public get id(): number {
|
||||
return this._assignment ? this._assignment.id : null;
|
||||
@ -29,35 +31,39 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
return this._assignment;
|
||||
}
|
||||
|
||||
public get polls(): ViewAssignmentPoll[] {
|
||||
return this._assignmentPolls;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.assignment.title;
|
||||
}
|
||||
|
||||
public get candidates(): ViewUser[] {
|
||||
return this._relatedUser;
|
||||
return this._assignmentRelatedUsers.map(aru => aru.user);
|
||||
}
|
||||
|
||||
public get agendaItem(): ViewItem {
|
||||
public get assignmentRelatedUsers(): ViewAssignmentRelatedUser[] {
|
||||
return this._assignmentRelatedUsers;
|
||||
}
|
||||
|
||||
public get agendaItem(): ViewItem | null {
|
||||
return this._agendaItem;
|
||||
}
|
||||
|
||||
public get tags(): ViewTag[] {
|
||||
return this._tags;
|
||||
return this._tags || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* unknown where the identifier to the phase is get
|
||||
*/
|
||||
public get phase(): number {
|
||||
return this.assignment ? this.assignment.phase : null;
|
||||
return this.assignment.phase;
|
||||
}
|
||||
|
||||
public get candidateAmount(): number {
|
||||
return this.candidates ? this.candidates.length : 0;
|
||||
}
|
||||
|
||||
public get polls(): Poll[] {
|
||||
return this.assignment ? this.assignment.polls : []; // TODO check
|
||||
return this._assignmentRelatedUsers ? this._assignmentRelatedUsers.length : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,18 +73,37 @@ export class ViewAssignment extends BaseAgendaViewModel {
|
||||
public getAgendaTitle;
|
||||
public getAgendaTitleWithType;
|
||||
|
||||
public constructor(assignment: Assignment, relatedUser?: ViewUser[], agendaItem?: ViewItem, tags?: ViewTag[]) {
|
||||
public constructor(
|
||||
assignment: Assignment,
|
||||
assignmentRelatedUsers: ViewAssignmentRelatedUser[],
|
||||
assignmentPolls: ViewAssignmentPoll[],
|
||||
agendaItem?: ViewItem,
|
||||
tags?: ViewTag[]
|
||||
) {
|
||||
super(Assignment.COLLECTIONSTRING);
|
||||
|
||||
console.log('related user: ', relatedUser);
|
||||
|
||||
this._assignment = assignment;
|
||||
this._relatedUser = relatedUser;
|
||||
this._assignmentRelatedUsers = assignmentRelatedUsers;
|
||||
this._assignmentPolls = assignmentPolls;
|
||||
this._agendaItem = agendaItem;
|
||||
this._tags = tags;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewItem && update.id === this.assignment.agenda_item_id) {
|
||||
this._agendaItem = update;
|
||||
} else if (update instanceof ViewTag && this.assignment.tags_id.includes(update.id)) {
|
||||
const tagIndex = this._tags.findIndex(_tag => _tag.id === update.id);
|
||||
if (tagIndex < 0) {
|
||||
this._tags.push(update);
|
||||
} else {
|
||||
this._tags[tagIndex] = update;
|
||||
}
|
||||
} else if (update instanceof ViewUser) {
|
||||
this.assignmentRelatedUsers.forEach(aru => aru.updateDependencies(update));
|
||||
this.polls.forEach(poll => poll.updateDependencies(update));
|
||||
}
|
||||
}
|
||||
|
||||
public getAgendaItem(): ViewItem {
|
||||
return this.agendaItem;
|
||||
|
@ -8,8 +8,8 @@ import {
|
||||
CalculablePollKey,
|
||||
PollVoteValue
|
||||
} from 'app/core/ui-services/poll.service';
|
||||
import { Poll } from 'app/shared/models/assignments/poll';
|
||||
import { PollOption } from 'app/shared/models/assignments/poll-option';
|
||||
import { ViewAssignmentPollOption } from '../models/view-assignment-poll-option';
|
||||
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
|
||||
|
||||
type AssignmentPollValues = 'auto' | 'votes' | 'yesnoabstain' | 'yesno';
|
||||
export type AssignmentPollMethod = 'yn' | 'yna' | 'votes';
|
||||
@ -69,15 +69,15 @@ export class AssignmentPollService extends PollService {
|
||||
* @param poll
|
||||
* @returns The amount of votes indicating the 100% base
|
||||
*/
|
||||
public getBaseAmount(poll: Poll): number | null {
|
||||
public getBaseAmount(poll: ViewAssignmentPoll): number | null {
|
||||
switch (this.percentBase) {
|
||||
case 'DISABLED':
|
||||
return null;
|
||||
case 'YES_NO':
|
||||
case 'YES_NO_ABSTAIN':
|
||||
if (poll.pollmethod === 'votes') {
|
||||
const yes = poll.options.map(cand => {
|
||||
const yesValue = cand.votes.find(v => v.value === 'Yes');
|
||||
const yes = poll.options.map(option => {
|
||||
const yesValue = option.votes.find(v => v.value === 'Yes');
|
||||
return yesValue ? yesValue.weight : -99;
|
||||
});
|
||||
if (Math.min(...yes) < 0) {
|
||||
@ -105,7 +105,7 @@ export class AssignmentPollService extends PollService {
|
||||
* @param value
|
||||
* @returns a percentage number with two digits, null if the value cannot be calculated
|
||||
*/
|
||||
public getPercent(poll: Poll, option: PollOption, value: PollVoteValue): number | null {
|
||||
public getPercent(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption, value: PollVoteValue): number | null {
|
||||
const base = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option);
|
||||
if (!base) {
|
||||
return null;
|
||||
@ -125,7 +125,7 @@ export class AssignmentPollService extends PollService {
|
||||
* @returns true if the poll has no percentages, the poll option is a special value,
|
||||
* or if the calculations are disabled in the config
|
||||
*/
|
||||
public isAbstractOption(poll: Poll, option: PollOption): boolean {
|
||||
public isAbstractOption(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption): boolean {
|
||||
if (!option.votes || !option.votes.length) {
|
||||
return true;
|
||||
}
|
||||
@ -146,7 +146,7 @@ export class AssignmentPollService extends PollService {
|
||||
* TODO: Yes, No, etc. in an option will always return true.
|
||||
* Use {@link isAbstractOption} for these
|
||||
*/
|
||||
public isAbstractValue(poll: Poll, value: CalculablePollKey): boolean {
|
||||
public isAbstractValue(poll: ViewAssignmentPoll, value: CalculablePollKey): boolean {
|
||||
if (!poll.pollBase || !this.pollValues.includes(value)) {
|
||||
return true;
|
||||
}
|
||||
@ -163,7 +163,7 @@ export class AssignmentPollService extends PollService {
|
||||
*
|
||||
* @returns an positive integer to be used as percentage base, or null
|
||||
*/
|
||||
private getOptionBaseAmount(poll: Poll, option: PollOption): number | null {
|
||||
private getOptionBaseAmount(poll: ViewAssignmentPoll, option: ViewAssignmentPollOption): number | null {
|
||||
if (poll.pollmethod === 'votes') {
|
||||
return null;
|
||||
}
|
||||
@ -193,7 +193,11 @@ export class AssignmentPollService extends PollService {
|
||||
* @param option
|
||||
* @returns a positive integer number; may return null if quorum is not calculable
|
||||
*/
|
||||
public yesQuorum(method: MajorityMethod, poll: Poll, option: PollOption): number | null {
|
||||
public yesQuorum(
|
||||
method: MajorityMethod,
|
||||
poll: ViewAssignmentPoll,
|
||||
option: ViewAssignmentPollOption
|
||||
): number | null {
|
||||
const baseAmount = poll.pollmethod === 'votes' ? poll.pollBase : this.getOptionBaseAmount(poll, option);
|
||||
return method.calc(baseAmount);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { Displayable } from './displayable';
|
||||
import { Identifiable } from '../../shared/models/base/identifiable';
|
||||
import { Collection } from 'app/shared/models/base/collection';
|
||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||
import { Updateable } from './updateable';
|
||||
|
||||
export interface ViewModelConstructor<T extends BaseViewModel> {
|
||||
COLLECTIONSTRING: string;
|
||||
@ -11,7 +12,7 @@ export interface ViewModelConstructor<T extends BaseViewModel> {
|
||||
/**
|
||||
* Base class for view models. alls view models should have titles.
|
||||
*/
|
||||
export abstract class BaseViewModel implements Displayable, Identifiable, Collection {
|
||||
export abstract class BaseViewModel implements Displayable, Identifiable, Collection, Updateable {
|
||||
/**
|
||||
* Force children to have an id.
|
||||
*/
|
||||
|
5
client/src/app/site/base/updateable.ts
Normal file
5
client/src/app/site/base/updateable.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { BaseViewModel } from './base-view-model';
|
||||
|
||||
export interface Updateable {
|
||||
updateDependencies(update: BaseViewModel): void;
|
||||
}
|
@ -707,13 +707,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
this.updateWorkflowIdForCreateForm();
|
||||
|
||||
const component = this;
|
||||
this.highlightedLineMatcher = new class implements ErrorStateMatcher {
|
||||
this.highlightedLineMatcher = new (class implements ErrorStateMatcher {
|
||||
public isErrorState(control: FormControl): boolean {
|
||||
const value: string = control && control.value ? control.value + '' : '';
|
||||
const maxLineNumber = component.repo.getLastLineNumber(component.motion, component.lineLength);
|
||||
return value.match(/[^\d]/) !== null || parseInt(value, 10) >= maxLineNumber;
|
||||
}
|
||||
}();
|
||||
})();
|
||||
|
||||
// create the search motion form
|
||||
this.recommendationExtensionForm = this.formBuilder.group({
|
||||
|
Loading…
Reference in New Issue
Block a user