rework motion submitters to clear confusion about creation and normal update.

Also docs and cleanup
This commit is contained in:
FinnStutzenstein 2018-11-30 09:24:07 +01:00
parent 1de2161ded
commit 212bce1c08
17 changed files with 249 additions and 118 deletions

View File

@ -36,7 +36,7 @@ export class ChoiceService extends OpenSlidesComponent {
): Promise<ChoiceAnswer> { ): Promise<ChoiceAnswer> {
const dialogRef = this.dialog.open(ChoiceDialogComponent, { const dialogRef = this.dialog.open(ChoiceDialogComponent, {
minWidth: '250px', minWidth: '250px',
maxHeight:'90vh', maxHeight: '90vh',
data: { title: title, choices: choices, multiSelect: multiSelect } data: { title: title, choices: choices, multiSelect: multiSelect }
}); });
return dialogRef.afterClosed().toPromise(); return dialogRef.afterClosed().toPromise();

View File

@ -28,5 +28,5 @@
<span translate>Ok</span> <span translate>Ok</span>
</button> </button>
<button mat-button (click)="closeDialog(false)"><span translate>Cancel</span></button> <button mat-button (click)="closeDialog(false)"><span translate>Cancel</span></button>
</mat-dialog-actions> </mat-dialog-actions>
</div> </div>

View File

@ -13,5 +13,5 @@ mat-radio-group {
} }
.scrollmenu-outer { .scrollmenu-outer {
max-height:inherit; max-height: inherit;
} }

View File

@ -14,9 +14,23 @@ type ChoiceDialogOption = (Identifiable & Displayable) | (Identifiable & { label
*/ */
export type ChoiceDialogOptions = (Identifiable & Displayable)[] | (Identifiable & { label: string })[]; export type ChoiceDialogOptions = (Identifiable & Displayable)[] | (Identifiable & { label: string })[];
/**
* All data needed for this dialog
*/
interface ChoiceDialogData { interface ChoiceDialogData {
/**
* A title to display
*/
title: string; title: string;
/**
* The choices to display
*/
choices: ChoiceDialogOptions; choices: ChoiceDialogOptions;
/**
* Select, if this should be a multiselect choice
*/
multiSelect: boolean; multiSelect: boolean;
} }
@ -50,6 +64,12 @@ export class ChoiceDialogComponent {
@Inject(MAT_DIALOG_DATA) public data: ChoiceDialogData @Inject(MAT_DIALOG_DATA) public data: ChoiceDialogData
) {} ) {}
/**
* Get the title from a choice. Maybe saved in a label property or using getTitle().
*
* @param choice The choice
* @return the title
*/
public getChoiceTitle(choice: ChoiceDialogOption): string { public getChoiceTitle(choice: ChoiceDialogOption): string {
if ('label' in choice) { if ('label' in choice) {
return choice.label; return choice.label;

View File

@ -23,7 +23,6 @@ export class Motion extends AgendaBaseModel {
public motion_block_id: number; public motion_block_id: number;
public origin: string; public origin: string;
public submitters: MotionSubmitter[]; public submitters: MotionSubmitter[];
public submitters_id: number[];
public supporters_id: number[]; public supporters_id: number[];
public comments: MotionComment[]; public comments: MotionComment[];
public workflow_id: number; public workflow_id: number;

View File

@ -9,8 +9,8 @@ import { TranslateService } from '@ngx-translate/core';
import { MotionRepositoryService } from '../../services/motion-repository.service'; import { MotionRepositoryService } from '../../services/motion-repository.service';
import { ViewMotion } from '../../models/view-motion'; import { ViewMotion } from '../../models/view-motion';
import { LinenumberingService } from '../../services/linenumbering.service'; import { LinenumberingService } from '../../services/linenumbering.service';
import { Motion } from '../../../../shared/models/motions/motion';
import { BaseViewComponent } from '../../../base/base-view'; import { BaseViewComponent } from '../../../base/base-view';
import { CreateMotion } from '../../models/create-motion';
/** /**
* Describes the single paragraphs from the base motion. * Describes the single paragraphs from the base motion.
@ -167,10 +167,10 @@ export class AmendmentCreateWizardComponent extends BaseViewComponent {
amendment_paragraphs: amendedParagraphs amendment_paragraphs: amendedParagraphs
}; };
const fromForm = new Motion(); const motion = new CreateMotion();
fromForm.deserialize(newMotionValues); motion.deserialize(newMotionValues);
const response = await this.repo.create(fromForm); const response = await this.repo.create(motion);
this.router.navigate(['./motions/' + response.id]); this.router.navigate(['./motions/' + response.id]);
} }
} }

View File

@ -29,6 +29,8 @@ import { ConfigService } from '../../../../core/services/config.service';
import { Workflow } from 'app/shared/models/motions/workflow'; import { Workflow } from 'app/shared/models/motions/workflow';
import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators'; import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators';
import { LocalPermissionsService } from '../../services/local-permissions.service'; import { LocalPermissionsService } from '../../services/local-permissions.service';
import { ViewCreateMotion } from '../../models/view-create-motion';
import { CreateMotion } from '../../models/create-motion';
/** /**
* Component for the motion detail view * Component for the motion detail view
@ -260,28 +262,10 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
} }
}); });
// load config variables // load config variables
this.configService.get('motions_statutes_enabled').subscribe( this.configService.get('motions_statutes_enabled').subscribe(enabled => (this.statutesEnabled = enabled));
(enabled: boolean): void => { this.configService.get('motions_min_supporters').subscribe(supporters => (this.minSupporters = supporters));
this.statutesEnabled = enabled; this.configService.get('motions_preamble').subscribe(preamble => (this.preamble = preamble));
} this.configService.get('motions_amendments_enabled').subscribe(enabled => (this.amendmentsEnabled = enabled));
);
this.configService.get('motions_min_supporters').subscribe(
(supporters: number): void => {
this.minSupporters = supporters;
}
);
this.configService.get('motions_preamble').subscribe(
(preamble: string): void => {
this.preamble = preamble;
}
);
this.configService.get('motions_amendments_enabled').subscribe(
(enabled: boolean): void => {
this.amendmentsEnabled = enabled;
}
);
} }
/** /**
@ -327,8 +311,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
// creates a new motion // creates a new motion
this.newMotion = true; this.newMotion = true;
this.editMotion = true; this.editMotion = true;
this.motion = new ViewMotion(); this.motion = new ViewCreateMotion();
this.motionCopy = new ViewMotion(); this.motionCopy = new ViewCreateMotion();
} else { } else {
// load existing motion // load existing motion
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
@ -393,7 +377,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
state_id: [''], state_id: [''],
recommendation_id: [''], recommendation_id: [''],
submitters_id: [], submitters_id: [],
supporters_id: [], supporters_id: [[]],
workflow_id: [], workflow_id: [],
origin: [''] origin: ['']
}); });
@ -419,45 +403,65 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
} }
/** /**
* Save a motion. Calls the "patchValues" function in the MotionObject * Before updating or creating, the motions needs to be prepared for paragraph based amendments.
* * A motion of type T is created, prepared and deserialized from the given motionValues
* http:post the motion to the server.
* The AutoUpdate-Service should see a change once it arrives and show it
* in the list view automatically
* *
* @param motionValues valus for the new motion
* @param ctor The motion constructor, so different motion types can be created.
*/ */
public async saveMotion(): Promise<void> { private prepareMotionForSave<T extends Motion>(motionValues: any, ctor: new (...args: any[]) => T): T {
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value }; const motion = new ctor();
const fromForm = new Motion();
if (this.motion.isParagraphBasedAmendment()) { if (this.motion.isParagraphBasedAmendment()) {
fromForm.amendment_paragraphs = this.motion.amendment_paragraphs.map( motion.amendment_paragraphs = this.motion.amendment_paragraphs.map(
(para: string): string => { (paragraph: string): string => {
if (para === null) { if (paragraph === null) {
return null; return null;
} else { } else {
return newMotionValues.text; return motionValues.text;
} }
} }
); );
newMotionValues.text = ''; motionValues.text = '';
} }
fromForm.deserialize(newMotionValues); motion.deserialize(motionValues);
return motion;
}
/**
* Creates a motion. Calls the "patchValues" function in the MotionObject
*/
public async createMotion(): Promise<void> {
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
const motion = this.prepareMotionForSave(newMotionValues, CreateMotion);
try { try {
if (this.newMotion) { const response = await this.repo.create(motion);
const response = await this.repo.create(fromForm); this.router.navigate(['./motions/' + response.id]);
this.router.navigate(['./motions/' + response.id]);
} else {
await this.repo.update(fromForm, this.motionCopy);
// if the motion was successfully updated, change the edit mode.
this.editMotion = false;
}
} catch (e) { } catch (e) {
this.raiseError(e); this.raiseError(e);
} }
} }
/**
* Save a motion. Calls the "patchValues" function in the MotionObject
*/
public async updateMotion(): Promise<void> {
const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value };
const motion = this.prepareMotionForSave(newMotionValues, Motion);
this.repo.update(motion, this.motionCopy).then(() => (this.editMotion = false), this.raiseError);
}
/**
* In the ui are no distinct buttons for update or create. This is decided here.
*/
public saveMotion(): void {
if (this.newMotion) {
this.createMotion();
} else {
this.updateMotion();
}
}
/** /**
* get the formated motion text from the repository. * get the formated motion text from the repository.
*/ */

View File

@ -42,7 +42,10 @@
<mat-cell *matCellDef="let motion"> <mat-cell *matCellDef="let motion">
<div class="innerTable"> <div class="innerTable">
<span class="motion-list-title">{{ motion.title }}</span> <br /> <span class="motion-list-title">{{ motion.title }}</span> <br />
<span class="motion-list-from"> <span translate>by</span> {{ motion.submitters }} </span> <br /> <span class="motion-list-from" *ngIf="motion.submitters.length">
<span translate>by</span> {{ motion.submitters }}
</span>
<br *ngIf="motion.submitters.length" />
<!-- state --> <!-- state -->
<mat-basic-chip <mat-basic-chip
[ngClass]="{ [ngClass]="{
@ -135,37 +138,37 @@
<mat-icon>sort</mat-icon> <mat-icon>sort</mat-icon>
<span translate>Move to agenda item</span> <span translate>Move to agenda item</span>
</button> </button>
<button mat-menu-item (click)="multiselectService.setStatus(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.setStatus(selectedRows))">
<mat-icon>label</mat-icon> <mat-icon>label</mat-icon>
<span translate>Set status</span> <span translate>Set status</span>
</button> </button>
<button mat-menu-item (click)="multiselectService.setRecommendation(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.setRecommendation(selectedRows))">
<mat-icon>report</mat-icon> <mat-icon>report</mat-icon>
<!-- TODO: better icon --> <!-- TODO: better icon -->
<span translate>Set recommendation</span> <span translate>Set recommendation</span>
</button> </button>
<button mat-menu-item (click)="multiselectService.setCategory(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.setCategory(selectedRows))">
<mat-icon>device_hub</mat-icon> <mat-icon>device_hub</mat-icon>
<!-- TODO: icon --> <!-- TODO: icon -->
<span translate>Set categories</span> <span translate>Set categories</span>
</button> </button>
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item (click)="multiselectService.addSubmitters(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.addSubmitters(selectedRows))">
<mat-icon>person_add</mat-icon> <mat-icon>person_add</mat-icon>
<!-- TODO: icon --> <!-- TODO: icon -->
<span translate>Add submitters</span> <span translate>Add submitters</span>
</button> </button>
<button mat-menu-item (click)="multiselectService.removeSubmitters(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.removeSubmitters(selectedRows))">
<mat-icon>person_outline</mat-icon> <mat-icon>person_outline</mat-icon>
<!-- TODO: icon --> <!-- TODO: icon -->
<span translate>remove submitters</span> <span translate>remove submitters</span>
</button> </button>
<button mat-menu-item (click)="multiselectService.addTags(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.addTags(selectedRows))">
<mat-icon>bookmarks</mat-icon> <mat-icon>bookmarks</mat-icon>
<!-- TODO: icon --> <!-- TODO: icon -->
<span translate>Add tags</span> <span translate>Add tags</span>
</button> </button>
<button mat-menu-item (click)="multiselectService.removeTags(selectedRows); toggleMultiSelect()"> <button mat-menu-item (click)="multiselectWrapper(multiselectService.removeTags(selectedRows))">
<mat-icon>bookmark_border</mat-icon> <mat-icon>bookmark_border</mat-icon>
<!-- TODO: icon --> <!-- TODO: icon -->
<span translate>Remove tags</span> <span translate>Remove tags</span>

View File

@ -172,4 +172,13 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
} }
return this.columnsToDisplayMinWidth; return this.columnsToDisplayMinWidth;
} }
/**
* Wraps multiselect actions to close the multiselect mode or throw an error if one happens.
*
* @param multiselectPromise The promise returned by multiselect actions.
*/
public multiselectWrapper(multiselectPromise: Promise<void>): void {
multiselectPromise.then(() => this.toggleMultiSelect());
}
} }

View File

@ -0,0 +1,13 @@
import { Motion } from 'app/shared/models/motions/motion';
/**
* Representation of Motion during creation. The submitters_id is added to send this information
* as an array of user ids to the server.
*/
export class CreateMotion extends Motion {
public submitters_id: number[];
public constructor(input?: any) {
super(input);
}
}

View File

@ -0,0 +1,62 @@
import { Category } from '../../../shared/models/motions/category';
import { User } from '../../../shared/models/users/user';
import { Workflow } from '../../../shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { Item } from 'app/shared/models/agenda/item';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { ViewMotion } from './view-motion';
import { CreateMotion } from './create-motion';
/**
* Create motion class for the View. Its different to ViewMotion in fact that the submitter handling is different
* on motion creation.
*
* @ignore
*/
export class ViewCreateMotion extends ViewMotion {
protected _motion: CreateMotion;
public get motion(): CreateMotion {
return this._motion;
}
public get submitters(): User[] {
return this._submitters;
}
public get submitters_id(): number[] {
return this.motion ? this.motion.submitters_id : null;
}
public set submitters(users: User[]) {
this._submitters = users;
this._motion.submitters_id = users.map(user => user.id);
}
public constructor(
motion?: CreateMotion,
category?: Category,
submitters?: User[],
supporters?: User[],
workflow?: Workflow,
state?: WorkflowState,
item?: Item,
block?: MotionBlock
) {
super(motion, category, submitters, supporters, workflow, state, item, block);
}
/**
* Duplicate this motion into a copy of itself
*/
public copy(): ViewCreateMotion {
return new ViewCreateMotion(
this._motion,
this._category,
this._submitters,
this._supporters,
this._workflow,
this._state
);
}
}

View File

@ -31,14 +31,14 @@ export enum ChangeRecoMode {
* @ignore * @ignore
*/ */
export class ViewMotion extends BaseViewModel { export class ViewMotion extends BaseViewModel {
private _motion: Motion; protected _motion: Motion;
private _category: Category; protected _category: Category;
private _submitters: User[]; protected _submitters: User[];
private _supporters: User[]; protected _supporters: User[];
private _workflow: Workflow; protected _workflow: Workflow;
private _state: WorkflowState; protected _state: WorkflowState;
private _item: Item; protected _item: Item;
private _block: MotionBlock; protected _block: MotionBlock;
/** /**
* Indicates the LineNumberingMode Mode. * Indicates the LineNumberingMode Mode.
@ -126,7 +126,7 @@ export class ViewMotion extends BaseViewModel {
} }
public get submitters_id(): number[] { public get submitters_id(): number[] {
return this.motion ? this.motion.submitters_id : null; return this.motion ? this.motion.submitterIds : null;
} }
public get supporters(): User[] { public get supporters(): User[] {
@ -153,10 +153,6 @@ export class ViewMotion extends BaseViewModel {
return this.motion && this.motion.state_id ? this.motion.state_id : null; return this.motion && this.motion.state_id ? this.motion.state_id : null;
} }
public get possibleStates(): WorkflowState[] {
return this.workflow ? this.workflow.states : null;
}
public get recommendation_id(): number { public get recommendation_id(): number {
return this.motion && this.motion.recommendation_id ? this.motion.recommendation_id : null; return this.motion && this.motion.recommendation_id ? this.motion.recommendation_id : null;
} }
@ -188,11 +184,6 @@ export class ViewMotion extends BaseViewModel {
this._motion.supporters_id = users.map(user => user.id); this._motion.supporters_id = users.map(user => user.id);
} }
public set submitters(users: User[]) {
this._submitters = users;
this._motion.submitters_id = users.map(user => user.id);
}
public get item(): Item { public get item(): Item {
return this._item; return this._item;
} }

View File

@ -12,13 +12,8 @@ import { BaseViewModel } from '../../base/base-view-model';
export class ViewWorkflow extends BaseViewModel { export class ViewWorkflow extends BaseViewModel {
private _workflow: Workflow; private _workflow: Workflow;
public constructor(workflow?: Workflow, id?: number, name?: string) { public constructor(workflow?: Workflow) {
super(); super();
if (!workflow) {
workflow = new Workflow();
workflow.id = id;
workflow.name = name;
}
this._workflow = workflow; this._workflow = workflow;
} }

View File

@ -10,6 +10,7 @@ import { UserRepositoryService } from 'app/site/users/services/user-repository.s
import { WorkflowRepositoryService } from './workflow-repository.service'; import { WorkflowRepositoryService } from './workflow-repository.service';
import { CategoryRepositoryService } from './category-repository.service'; import { CategoryRepositoryService } from './category-repository.service';
import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service'; import { TagRepositoryService } from 'app/site/tags/services/tag-repository.service';
import { HttpService } from 'app/core/services/http.service';
/** /**
* Contains all multiselect actions for the motion list view. * Contains all multiselect actions for the motion list view.
@ -38,7 +39,8 @@ export class MotionMultiselectService {
private userRepo: UserRepositoryService, private userRepo: UserRepositoryService,
private workflowRepo: WorkflowRepositoryService, private workflowRepo: WorkflowRepositoryService,
private categoryRepo: CategoryRepositoryService, private categoryRepo: CategoryRepositoryService,
private tagRepo: TagRepositoryService private tagRepo: TagRepositoryService,
private httpService: HttpService
) {} ) {}
/** /**
@ -88,11 +90,16 @@ export class MotionMultiselectService {
id: workflowState.id, id: workflowState.id,
label: workflowState.recommendation_label label: workflowState.recommendation_label
})); }));
choices.push({ id: 0, label: 'Delete recommendation' });
const selectedChoice = await this.choiceService.open(title, choices); const selectedChoice = await this.choiceService.open(title, choices);
if (selectedChoice) { if (typeof selectedChoice === 'number') {
for (const motion of motions) { const requestData = motions.map(motion => ({
await this.repo.setRecommendation(motion, selectedChoice as number); id: motion.id,
} recommendation: selectedChoice
}));
await this.httpService.post('/rest/motions/motion/manage_multiple_recommendation', {
motions: requestData
});
} }
} }
@ -120,7 +127,15 @@ export class MotionMultiselectService {
const title = this.translate.instant('This will add the following submitters of all selected motions:'); const title = this.translate.instant('This will add the following submitters of all selected motions:');
const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true); const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true);
if (selectedChoice) { if (selectedChoice) {
throw new Error('Not implemented on the server'); const requestData = motions.map(motion => {
let submitterIds = [...motion.submitters_id, ...(selectedChoice as number[])];
submitterIds = submitterIds.filter((id, index, self) => self.indexOf(id) === index); // remove duplicates
return {
id: motion.id,
submitters: submitterIds
};
});
await this.httpService.post('/rest/motions/motion/manage_multiple_submitters', { motions: requestData });
} }
} }
@ -133,7 +148,15 @@ export class MotionMultiselectService {
const title = this.translate.instant('This will remove the following submitters from all selected motions:'); const title = this.translate.instant('This will remove the following submitters from all selected motions:');
const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true); const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true);
if (selectedChoice) { if (selectedChoice) {
throw new Error('Not implemented on the server'); const requestData = motions.map(motion => {
const submitterIdsToRemove = selectedChoice as number[];
const submitterIds = motion.submitters_id.filter(id => !submitterIdsToRemove.includes(id));
return {
id: motion.id,
submitters: submitterIds
};
});
await this.httpService.post('/rest/motions/motion/manage_multiple_submitters', { motions: requestData });
} }
} }
@ -146,11 +169,15 @@ export class MotionMultiselectService {
const title = this.translate.instant('This will add the following tags to all selected motions:'); const title = this.translate.instant('This will add the following tags to all selected motions:');
const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true); const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true);
if (selectedChoice) { if (selectedChoice) {
for (const motion of motions) { const requestData = motions.map(motion => {
let tagIds = [...motion.tags_id, ...(selectedChoice as number[])]; let tagIds = [...motion.tags_id, ...(selectedChoice as number[])];
tagIds = tagIds.filter((id, index, self) => self.indexOf(id) === index); tagIds = tagIds.filter((id, index, self) => self.indexOf(id) === index); // remove duplicates
await this.repo.update({ tags_id: tagIds }, motion); return {
} id: motion.id,
tags: tagIds
};
});
await this.httpService.post('/rest/motions/motion/manage_multiple_tags', { motions: requestData });
} }
} }
@ -163,11 +190,15 @@ export class MotionMultiselectService {
const title = this.translate.instant('This will remove the following tags from all selected motions:'); const title = this.translate.instant('This will remove the following tags from all selected motions:');
const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true); const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true);
if (selectedChoice) { if (selectedChoice) {
for (const motion of motions) { const requestData = motions.map(motion => {
const tagIdsToRemove = selectedChoice as number[]; const tagIdsToRemove = selectedChoice as number[];
const tagIds = motion.tags_id.filter(id => !tagIdsToRemove.includes(id)); const tagIds = motion.tags_id.filter(id => !tagIdsToRemove.includes(id));
await this.repo.update({ tags_id: tagIds }, motion); return {
} id: motion.id,
tags: tagIds
};
});
await this.httpService.post('/rest/motions/motion/manage_multiple_tags', { motions: requestData });
} }
} }
} }

View File

@ -25,6 +25,7 @@ import { Item } from 'app/shared/models/agenda/item';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component'; import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
import { TreeService } from 'app/core/services/tree.service'; import { TreeService } from 'app/core/services/tree.service';
import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph'; import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph';
import { CreateMotion } from '../models/create-motion';
/** /**
* Repository Services for motions (and potentially categories) * Repository Services for motions (and potentially categories)
@ -109,12 +110,8 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* *
* @param update the form data containing the updated values * @param update the form data containing the updated values
* @param viewMotion The View Motion. If not present, a new motion will be created * @param viewMotion The View Motion. If not present, a new motion will be created
* TODO: Remove the viewMotion and make it actually distignuishable from save()
*/ */
public async create(motion: Motion): Promise<Identifiable> { public async create(motion: CreateMotion): Promise<Identifiable> {
if (!motion.supporters_id) {
delete motion.supporters_id;
}
return await this.dataSend.createModel(motion); return await this.dataSend.createModel(motion);
} }

View File

@ -29,7 +29,7 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
* @param dataSend * @param dataSend
*/ */
public constructor( public constructor(
protected DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringModelMapperService, mapperService: CollectionStringModelMapperService,
private dataSend: DataSendService private dataSend: DataSendService
) { ) {
@ -61,17 +61,11 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
} }
/** /**
* Returns the workflow for the ID * Collects all existing states from all workflows
* @param workflow_id workflow ID
*/ */
public getWorkflowByID(workflow_id: number): Workflow {
const wfList = this.DS.getAll(Workflow);
return wfList.find(workflow => workflow.id === workflow_id);
}
public getAllWorkflowStates(): WorkflowState[] { public getAllWorkflowStates(): WorkflowState[] {
let states: WorkflowState[] = []; let states: WorkflowState[] = [];
this.DS.getAll(Workflow).forEach(workflow => { this.getViewModelList().forEach(workflow => {
states = states.concat(workflow.states); states = states.concat(workflow.states);
}); });
return states; return states;

View File

@ -105,12 +105,25 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
return pw; return pw;
} }
/**
* Updates the default password and sets the real password.
*
* @param user The user to update
* @param password The password to set
*/
public async resetPassword(user: ViewUser, password: string): Promise<void> { public async resetPassword(user: ViewUser, password: string): Promise<void> {
await this.update({ default_password: password }, user); await this.update({ default_password: password }, user);
const path = `/rest/users/user/${user.id}/reset_password/`; const path = `/rest/users/user/${user.id}/reset_password/`;
await this.httpService.post(path, { password: password }); await this.httpService.post(path, { password: password });
} }
/**
* Sends invitation emails to all given users. Returns a prepared string to show the user.
* This string should always be shown, becuase even in success cases, some users may not get
* an email and the user should be notified about this.
*
* @param users All affected users
*/
public async sendInvitationEmail(users: ViewUser[]): Promise<string> { public async sendInvitationEmail(users: ViewUser[]): Promise<string> {
const user_ids = users.map(user => user.id); const user_ids = users.map(user => user.id);
const subject = this.translate.instant(this.configService.instant('users_email_subject')); const subject = this.translate.instant(this.configService.instant('users_email_subject'));