import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CategoryRepositoryService } from 'app/core/repositories/motions/category-repository.service'; import { ChoiceDialogOptions } from 'app/shared/components/choice-dialog/choice-dialog.component'; import { ChoiceService } from 'app/core/ui-services/choice.service'; import { Displayable } from 'app/site/base/displayable'; import { HttpService } from 'app/core/core-services/http.service'; import { Identifiable } from 'app/shared/models/base/identifiable'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service'; import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service'; import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { PersonalNoteService } from 'app/core/ui-services/personal-note.service'; import { PromptService } from 'app/core/ui-services/prompt.service'; import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service'; import { TreeService } from 'app/core/ui-services/tree.service'; import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service'; import { ViewMotion } from '../models/view-motion'; import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service'; /** * Contains all multiselect actions for the motion list view. */ @Injectable({ providedIn: 'root' }) export class MotionMultiselectService { /** * Does nothing. * * @param repo MotionRepositoryService * @param translate TranslateService * @param promptService * @param choiceService * @param userRepo * @param workflowRepo * @param categoryRepo * @param tagRepo * @param agendaRepo * @param motionBlockRepo * @param httpService * @param treeService * @param personalNoteService */ public constructor( private repo: MotionRepositoryService, private translate: TranslateService, private promptService: PromptService, private choiceService: ChoiceService, private userRepo: UserRepositoryService, private workflowRepo: WorkflowRepositoryService, private categoryRepo: CategoryRepositoryService, private tagRepo: TagRepositoryService, private agendaRepo: ItemRepositoryService, private motionBlockRepo: MotionBlockRepositoryService, private httpService: HttpService, private treeService: TreeService, private personalNoteService: PersonalNoteService ) {} /** * Deletes the given motions. Asks for confirmation. * * @param motions The motions to delete */ public async delete(motions: ViewMotion[]): Promise { const title = this.translate.instant('Are you sure you want to delete all selected motions?'); if (await this.promptService.open(title, null)) { for (const motion of motions) { await this.repo.delete(motion); } } } /** * Moves the related agenda items from the motions as childs under a selected (parent) agenda item. */ public async moveToItem(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will move all selected motions as childs to:'); const choices: (Displayable & Identifiable)[] = this.agendaRepo.getSortedViewModelList(); const selectedChoice = await this.choiceService.open(title, choices); if (selectedChoice) { const requestData = { items: motions.map(motion => motion.agenda_item_id), parent_id: selectedChoice.items as number }; await this.httpService.post('/rest/agenda/item/assign/', requestData); } } /** * Opens a dialog and then sets the status for all motions. * * @param motions The motions to change */ public async setStateOfMultiple(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will set the following state for all selected motions:'); const choices = this.workflowRepo.getWorkflowStatesForMotions(motions).map(workflowState => ({ id: workflowState.id, label: workflowState.name })); const selectedChoice = await this.choiceService.open(title, choices); if (selectedChoice) { await this.repo.setMultiState(motions, selectedChoice.items as number); } } /** * Opens a dialog and sets the recommendation to the users choice for all selected motions. * * @param motions The motions to change */ public async setRecommendation(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will set the following recommendation for all selected motions:'); const choices = this.workflowRepo .getWorkflowStatesForMotions(motions) .filter(workflowState => !!workflowState.recommendation_label) .map(workflowState => ({ id: workflowState.id, label: workflowState.recommendation_label })); const clearChoice = this.translate.instant('Delete recommendation'); const selectedChoice = await this.choiceService.open(title, choices, false, null, clearChoice); if (selectedChoice) { const requestData = motions.map(motion => ({ id: motion.id, recommendation: selectedChoice.action ? 0 : (selectedChoice.items as number) })); await this.httpService.post('/rest/motions/motion/manage_multiple_recommendation/', { motions: requestData }); } } /** * Opens a dialog and sets the category for all given motions. * * @param motions The motions to change */ public async setCategory(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will set the following category for all selected motions:'); const clearChoice = this.translate.instant('No category'); const selectedChoice = await this.choiceService.open( title, this.categoryRepo.getSortedViewModelList(), false, null, clearChoice ); if (selectedChoice) { for (const motion of motions) { await this.repo.update( { category_id: selectedChoice.action ? null : (selectedChoice.items as number) }, motion ); } } } /** * Opens a dialog and adds or removes the selected submitters for all given motions. * * @param motions The motions to add/remove the sumbitters to */ public async changeSubmitters(motions: ViewMotion[]): Promise { const title = this.translate.instant( 'This will add or remove the following submitters for all selected motions:' ); const choices = [this.translate.instant('Add'), this.translate.instant('Remove')]; const selectedChoice = await this.choiceService.open( title, this.userRepo.getSortedViewModelList(), true, choices ); if (selectedChoice && selectedChoice.action === choices[0]) { const requestData = motions.map(motion => { let submitterIds = [...motion.sorted_submitters_id, ...(selectedChoice.items 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 }); } else if (selectedChoice && selectedChoice.action === choices[1]) { const requestData = motions.map(motion => { const submitterIdsToRemove = selectedChoice.items as number[]; const submitterIds = motion.sorted_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 }); } } /** * Opens a dialog and adds/removes the selected tags for all given motions. * * @param motions The motions to add the tags to */ public async changeTags(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will add or remove the following tags for all selected motions:'); const choices = [ this.translate.instant('Add'), this.translate.instant('Remove'), this.translate.instant('Clear tags') ]; const selectedChoice = await this.choiceService.open( title, this.tagRepo.getSortedViewModelList(), true, choices ); if (selectedChoice && selectedChoice.action === choices[0]) { const requestData = motions.map(motion => { let tagIds = [...motion.tags_id, ...(selectedChoice.items as number[])]; tagIds = tagIds.filter((id, index, self) => self.indexOf(id) === index); // remove duplicates return { id: motion.id, tags: tagIds }; }); await this.httpService.post('/rest/motions/motion/manage_multiple_tags/', { motions: requestData }); } else if (selectedChoice && selectedChoice.action === choices[1]) { const requestData = motions.map(motion => { const tagIdsToRemove = selectedChoice.items as number[]; const tagIds = motion.tags_id.filter(id => !tagIdsToRemove.includes(id)); return { id: motion.id, tags: tagIds }; }); await this.httpService.post('/rest/motions/motion/manage_multiple_tags/', { motions: requestData }); } else if (selectedChoice && selectedChoice.action === choices[2]) { const requestData = motions.map(motion => { return { id: motion.id, tags: [] }; }); await this.httpService.post('/rest/motions/motion/manage_multiple_tags/', { motions: requestData }); } } /** * Opens a dialog and changes the motionBlock for all given motions. * * @param motions The motions for which to change the motionBlock */ public async setMotionBlock(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will set the following motion block for all selected motions:'); const clearChoice = this.translate.instant('Clear motion block'); const selectedChoice = await this.choiceService.open( title, this.motionBlockRepo.getSortedViewModelList(), false, null, clearChoice ); if (selectedChoice) { for (const motion of motions) { const blockId = selectedChoice.action ? null : (selectedChoice.items as number); await this.repo.update({ motion_block_id: blockId }, motion); } } } /** * Triggers the selected motions to be moved in the call-list (sort_parent, weight) * as children or as following after a selected motion. * * @param motions The motions to move */ public async bulkMoveItems(motions: ViewMotion[]): Promise { const title = this.translate.instant( 'This will move all selected motions under or after the following motion in the call list:' ); const options = [this.translate.instant('Set as parent'), this.translate.instant('Insert after')]; const allMotions = this.repo.getViewModelList(); const tree = this.treeService.makeTree(allMotions, 'weight', 'sort_parent_id'); const itemsToMove = this.treeService.getTopItemsFromTree(tree, motions); const selectableItems = this.treeService.getTreeWithoutSelection(tree, motions); const selectedChoice = await this.choiceService.open(title, selectableItems, false, options); if (selectedChoice) { if (selectedChoice.action === options[0]) { // set choice as parent this.repo.sortMotionBranches(itemsToMove, selectedChoice.items as number); } else if (selectedChoice.action === options[1]) { // insert after chosen const olderSibling = this.repo.getViewModel(selectedChoice.items as number); const parentId = olderSibling ? olderSibling.sort_parent_id : null; const siblings = allMotions.filter(motion => motion.sort_parent_id === parentId); const idx = siblings.findIndex(sib => sib.id === olderSibling.id); const before = siblings.slice(0, idx + 1); const after = siblings.slice(idx + 1); const sum = [].concat(before, itemsToMove, after); this.repo.sortMotionBranches(sum, parentId); } } } /** * Bulk sets/unsets the favorite status (after a confirmation dialog) * * @param motions The motions to set/unset the favorite status for */ public async bulkSetFavorite(motions: ViewMotion[]): Promise { const title = this.translate.instant('This will set the favorite status for all selected motions:'); const choices: ChoiceDialogOptions = [ { id: 1, label: this.translate.instant('Set as favorite') }, { id: 2, label: this.translate.instant('Set as not favorite') } ]; const selectedChoice = await this.choiceService.open(title, choices); if (selectedChoice && motions.length) { const star = (selectedChoice.items as number) === choices[0].id; await this.personalNoteService.bulkSetStar(motions, star); } } }