Merge pull request #3957 from CatoTH/StatuteParagraphAmendments
Creating / Editing / Showing statute paragraph amendments
This commit is contained in:
commit
01c593e9be
@ -115,6 +115,7 @@ _('Default text version for change recommendations');
|
||||
// subgroup Amendments
|
||||
_('Amendments');
|
||||
_('Activate amendments');
|
||||
_('Activate statutes');
|
||||
_('Show amendments together with motions');
|
||||
_('Prefix for the identifier for amendments');
|
||||
_('The title of the motion is always applied.');
|
||||
|
@ -30,6 +30,7 @@ export class Motion extends AgendaBaseModel {
|
||||
public state_id: number;
|
||||
public state_extension: string;
|
||||
public state_required_permission_to_see: string;
|
||||
public statute_paragraph_id: number;
|
||||
public recommendation_id: number;
|
||||
public recommendation_extension: string;
|
||||
public tags_id: number[];
|
||||
|
@ -259,7 +259,7 @@
|
||||
<form class="motion-content" [formGroup]='contentForm' (ngSubmit)='saveMotion()'>
|
||||
|
||||
<!-- Line Number and Diff buttons-->
|
||||
<div *ngIf="!editMotion" class="motion-text-controls">
|
||||
<div *ngIf="motion && !editMotion && !motion.isStatuteAmendment()" class="motion-text-controls">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="lineNumberingMenu" matTooltip="{{ 'Line numbering' | translate }}">
|
||||
<mat-icon>format_list_numbered</mat-icon>
|
||||
</button>
|
||||
@ -268,11 +268,29 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Selecting statute paragraphs for amendment -->
|
||||
<div class="statute-amendment-selector" *ngIf="editMotion && statuteParagraphs.length > 0 && statutesEnabled">
|
||||
<mat-checkbox formControlName='statute_amendment' translate (change)="onStatuteAmendmentChange($event)">
|
||||
Statute amendment
|
||||
</mat-checkbox>
|
||||
|
||||
<mat-form-field *ngIf="contentForm.value.statute_amendment">
|
||||
<mat-select [placeholder]="'Select paragraph to amend' | translate"
|
||||
formControlName='statute_paragraph_id'
|
||||
(valueChange)="onStatuteParagraphChange($event)">
|
||||
<mat-option *ngFor="let paragraph of statuteParagraphs" [value]="paragraph.id">
|
||||
{{ paragraph.title }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<div *ngIf="motion && motion.title || editMotion">
|
||||
<div *ngIf='!editMotion'>
|
||||
<h4>{{motion.title}}</h4>
|
||||
</div>
|
||||
|
||||
<mat-form-field *ngIf="editMotion" class="wide-form">
|
||||
<input matInput osAutofocus placeholder="{{ 'Title' | translate }}" formControlName='title' [value]='motionCopy.title'
|
||||
required>
|
||||
@ -283,7 +301,7 @@
|
||||
<!-- Text -->
|
||||
<!-- TODO: this is a config variable. Read it out -->
|
||||
<span class="text-prefix-label" translate>The assembly may decide:</span>
|
||||
<ng-container *ngIf='motion && !editMotion'>
|
||||
<ng-container *ngIf='motion && !editMotion && !motion.isStatuteAmendment()'>
|
||||
<div *ngIf="!isRecoModeDiff()" class="motion-text" [class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()" [class.line-numbers-outside]="isLineNumberingOutside()">
|
||||
<os-motion-detail-original-change-recommendations *ngIf="isLineNumberingOutside() && isRecoModeOriginal()"
|
||||
@ -294,6 +312,10 @@
|
||||
<os-motion-detail-diff *ngIf="isRecoModeDiff()" [motion]="motion" [changes]="allChangingObjects"
|
||||
[scrollToChange]="scrollToChange" (createChangeRecommendation)="createChangeRecommendation($event)"></os-motion-detail-diff>
|
||||
</ng-container>
|
||||
<div class="motion-text line-numbers-none" *ngIf="motion && !editMotion && motion.isStatuteAmendment()"
|
||||
[innerHTML]="getFormattedStatuteAmendment()">
|
||||
</div>
|
||||
|
||||
<mat-form-field *ngIf="motion && editMotion" class="wide-form">
|
||||
<textarea matInput placeholder="{{ 'Motion text' | translate }}" formControlName='text' [value]='motionCopy.text' required></textarea>
|
||||
</mat-form-field>
|
||||
|
@ -113,6 +113,12 @@ span {
|
||||
}
|
||||
}
|
||||
|
||||
.statute-amendment-selector {
|
||||
mat-form-field {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.motion-content {
|
||||
h4 {
|
||||
margin: 10px 10px 15px 0;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatDialog, MatExpansionPanel, MatSnackBar, MatSelectChange } from '@angular/material';
|
||||
import { MatDialog, MatExpansionPanel, MatSnackBar, MatSelectChange, MatCheckboxChange } from '@angular/material';
|
||||
|
||||
import { Category } from '../../../../shared/models/motions/category';
|
||||
import { ViewportService } from '../../../../core/services/viewport.service';
|
||||
@ -23,6 +23,9 @@ import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser';
|
||||
import { ViewUnifiedChange } from '../../models/view-unified-change';
|
||||
import { OperatorService } from '../../../../core/services/operator.service';
|
||||
import { BaseViewComponent } from '../../../base/base-view';
|
||||
import { ViewStatuteParagraph } from "../../models/view-statute-paragraph";
|
||||
import { StatuteParagraphRepositoryService } from "../../services/statute-paragraph-repository.service";
|
||||
import { ConfigService } from "../../../../core/services/config.service";
|
||||
|
||||
/**
|
||||
* Component for the motion detail view
|
||||
@ -72,6 +75,12 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
public motion: ViewMotion;
|
||||
|
||||
/**
|
||||
* Value of the configuration variable `motions_statutes_enabled` - are statutes enabled?
|
||||
* @TODO replace by direct access to config variable, once it's available from the templates
|
||||
*/
|
||||
public statutesEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Copy of the motion that the user might edit
|
||||
*/
|
||||
@ -102,6 +111,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
*/
|
||||
public previousMotion: ViewMotion;
|
||||
|
||||
/**
|
||||
* statute paragraphs, necessary for amendments
|
||||
*/
|
||||
public statuteParagraphs: ViewStatuteParagraph[] = [];
|
||||
|
||||
/**
|
||||
* Subject for the Categories
|
||||
*/
|
||||
@ -134,14 +148,16 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
* @param translate
|
||||
* @param matSnackBar
|
||||
* @param vp the viewport service
|
||||
* @param op
|
||||
* @param op Operator Service
|
||||
* @param router to navigate back to the motion list and to an existing motion
|
||||
* @param route determine if this is a new or an existing motion
|
||||
* @param formBuilder For reactive forms. Form Group and Form Control
|
||||
* @param dialogService For opening dialogs
|
||||
* @param repo Motion Repository
|
||||
* @param changeRecoRepo Change Recommendation Repository
|
||||
* @param statuteRepo: Statute Paragraph Repository
|
||||
* @param DS The DataStoreService
|
||||
* @param configService The configuration provider
|
||||
* @param sanitizer For making HTML SafeHTML
|
||||
*/
|
||||
public constructor(
|
||||
@ -156,7 +172,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
private dialogService: MatDialog,
|
||||
private repo: MotionRepositoryService,
|
||||
private changeRecoRepo: ChangeRecommendationRepositoryService,
|
||||
private statuteRepo: StatuteParagraphRepositoryService,
|
||||
private DS: DataStoreService,
|
||||
private configService: ConfigService,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
@ -178,6 +196,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
this.categoryObserver.next(DS.getAll(Category));
|
||||
}
|
||||
});
|
||||
this.configService.get('motions_statutes_enabled').subscribe((enabled: boolean): void => {
|
||||
this.statutesEnabled = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,10 +258,12 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
});
|
||||
this.metaInfoForm.patchValue(metaInfoPatch);
|
||||
|
||||
const contentPatch = {};
|
||||
const contentPatch: {[key: string]: any} = {};
|
||||
Object.keys(this.contentForm.controls).forEach(ctrl => {
|
||||
contentPatch[ctrl] = formMotion[ctrl];
|
||||
});
|
||||
const statuteAmendmentFieldName = 'statute_amendment';
|
||||
contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment();
|
||||
this.contentForm.patchValue(contentPatch);
|
||||
}
|
||||
|
||||
@ -262,7 +285,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
this.contentForm = this.formBuilder.group({
|
||||
title: ['', Validators.required],
|
||||
text: ['', Validators.required],
|
||||
reason: ['']
|
||||
reason: [''],
|
||||
statute_amendment: [''], // Internal value for the checkbox, not saved to the model
|
||||
statute_paragraph_id: ['']
|
||||
});
|
||||
}
|
||||
|
||||
@ -311,12 +336,22 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* get the formated motion text from the repository, as SafeHTML for [innerHTML]
|
||||
* get the formatted motion text from the repository, as SafeHTML for [innerHTML]
|
||||
* @returns {SafeHtml}
|
||||
*/
|
||||
public getFormattedText(): SafeHtml {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(this.getFormattedTextPlain());
|
||||
}
|
||||
|
||||
/**
|
||||
* get the diff html from the statute amendment, as SafeHTML for [innerHTML]
|
||||
* @returns {SafeHtml}
|
||||
*/
|
||||
public getFormattedStatuteAmendment(): SafeHtml {
|
||||
const diffHtml = this.repo.formatStatuteAmendment(this.statuteParagraphs, this.motion, this.motion.lineLength);
|
||||
return this.sanitizer.bypassSecurityTrustHtml(diffHtml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger to delete the motion.
|
||||
* Sends a delete request over the repository and
|
||||
@ -426,6 +461,28 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the checkbox is deactivated, the statute_paragraph_id-field needs to be reset, as only that field is saved
|
||||
* @param {MatCheckboxChange} $event
|
||||
*/
|
||||
public onStatuteAmendmentChange($event: MatCheckboxChange): void {
|
||||
this.contentForm.patchValue({
|
||||
statute_paragraph_id: null
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The paragraph of the statute to amend was changed -> change the input fields below
|
||||
* @param {number} newValue
|
||||
*/
|
||||
public onStatuteParagraphChange(newValue: number): void {
|
||||
const selectedParagraph = this.statuteParagraphs.find(par => par.id === newValue);
|
||||
this.contentForm.patchValue({
|
||||
title: this.translate.instant('Statute amendment for') + ` ${selectedParagraph.title}`,
|
||||
text: selectedParagraph.text
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates the user to the given ViewMotion
|
||||
* @param motion target
|
||||
@ -511,5 +568,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
|
||||
this.setSurroundingMotions();
|
||||
}
|
||||
});
|
||||
this.statuteRepo.getViewModelListObservable().subscribe(newViewStatuteParagraphs => {
|
||||
this.statuteParagraphs = newViewStatuteParagraphs;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@
|
||||
<span translate>Comment sections</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item routerLink="statute-paragraphs">
|
||||
<button mat-menu-item routerLink="statute-paragraphs" *ngIf="statutesEnabled">
|
||||
<mat-icon>account_balance</mat-icon>
|
||||
<span translate>Statute paragraphs</span>
|
||||
</button>
|
||||
|
@ -9,6 +9,7 @@ import { ViewMotion } from '../../models/view-motion';
|
||||
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
|
||||
import { ListViewBaseComponent } from '../../../base/list-view-base';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { ConfigService } from "../../../../core/services/config.service";
|
||||
|
||||
/**
|
||||
* Component that displays all the motions in a Table using DataSource.
|
||||
@ -31,13 +32,21 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
*/
|
||||
public columnsToDisplayFullWidth = ['identifier', 'title', 'state', 'speakers'];
|
||||
|
||||
/**
|
||||
* Value of the configuration variable `motions_statutes_enabled` - are statutes enabled?
|
||||
* @TODO replace by direct access to config variable, once it's available from the templates
|
||||
*/
|
||||
public statutesEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Constructor implements title and translation Module.
|
||||
*
|
||||
* @param titleService Title
|
||||
* @param translate Translation
|
||||
* @param matSnackBar
|
||||
* @param router Router
|
||||
* @param route Current route
|
||||
* @param configService The configuration provider
|
||||
* @param repo Motion Repository
|
||||
*/
|
||||
public constructor(
|
||||
@ -46,6 +55,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
matSnackBar: MatSnackBar,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private configService: ConfigService,
|
||||
private repo: MotionRepositoryService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
@ -69,6 +79,9 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion> imple
|
||||
}
|
||||
});
|
||||
});
|
||||
this.configService.get('motions_statutes_enabled').subscribe((enabled: boolean): void => {
|
||||
this.statutesEnabled = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,6 +138,10 @@ export class ViewMotion extends BaseViewModel {
|
||||
return this.motion && this.motion.recommendation_id ? this.motion.recommendation_id : null;
|
||||
}
|
||||
|
||||
public get statute_paragraph_id(): number {
|
||||
return this.motion && this.motion.statute_paragraph_id ? this.motion.statute_paragraph_id : null;
|
||||
}
|
||||
|
||||
public get recommendation(): WorkflowState {
|
||||
return this.recommendation_id && this.workflow ? this.workflow.getStateById(this.recommendation_id) : null;
|
||||
}
|
||||
@ -269,6 +273,10 @@ export class ViewMotion extends BaseViewModel {
|
||||
return !!(this.supporters && this.supporters.length > 0);
|
||||
}
|
||||
|
||||
public isStatuteAmendment(): boolean {
|
||||
return !!this.statute_paragraph_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate this motion into a copy of itself
|
||||
*/
|
||||
|
@ -14,6 +14,7 @@ import { DiffService, LineRange, ModificationType } from './diff.service';
|
||||
import { ViewChangeReco } from '../models/view-change-reco';
|
||||
import { MotionChangeReco } from '../../../shared/models/motions/motion-change-reco';
|
||||
import { ViewUnifiedChange } from '../models/view-unified-change';
|
||||
import { ViewStatuteParagraph } from '../models/view-statute-paragraph';
|
||||
import { Identifiable } from '../../../shared/models/base/identifiable';
|
||||
import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
|
||||
import { HttpService } from 'app/core/services/http.service';
|
||||
@ -225,6 +226,13 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
|
||||
}
|
||||
}
|
||||
|
||||
public formatStatuteAmendment(paragraphs: ViewStatuteParagraph[], amendment: ViewMotion, lineLength: number): string {
|
||||
const origParagraph = paragraphs.find(paragraph => paragraph.id === amendment.statute_paragraph_id);
|
||||
let diffHtml = this.diff.diff(origParagraph.text, amendment.text);
|
||||
diffHtml = this.lineNumbering.insertLineBreaksWithoutNumbers(diffHtml, lineLength, true);
|
||||
return diffHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a renderable HTML string representing the given line number range of this motion
|
||||
*
|
||||
|
@ -138,6 +138,17 @@ def get_config_variables():
|
||||
group='Motions',
|
||||
subgroup='General')
|
||||
|
||||
# Statutes
|
||||
|
||||
yield ConfigVariable(
|
||||
name='motions_statutes_enabled',
|
||||
default_value=False,
|
||||
input_type='boolean',
|
||||
label='Activate statutes',
|
||||
weight=334,
|
||||
group='Motions',
|
||||
subgroup='General')
|
||||
|
||||
# Amendments
|
||||
yield ConfigVariable(
|
||||
name='motions_amendments_enabled',
|
||||
|
@ -406,6 +406,7 @@ class MotionSerializer(ModelSerializer):
|
||||
'state',
|
||||
'state_extension',
|
||||
'state_required_permission_to_see',
|
||||
'statute_paragraph',
|
||||
'workflow_id',
|
||||
'recommendation',
|
||||
'recommendation_extension',
|
||||
@ -461,6 +462,7 @@ class MotionSerializer(ModelSerializer):
|
||||
motion.motion_block = validated_data.get('motion_block')
|
||||
motion.origin = validated_data.get('origin', '')
|
||||
motion.parent = validated_data.get('parent')
|
||||
motion.statute_paragraph = validated_data.get('statute_paragraph')
|
||||
motion.reset_state(validated_data.get('workflow_id'))
|
||||
motion.agenda_item_update_information['type'] = validated_data.get('agenda_type')
|
||||
motion.agenda_item_update_information['parent_id'] = validated_data.get('agenda_parent_id')
|
||||
|
Loading…
Reference in New Issue
Block a user