+
format_list_numbered
@@ -268,11 +268,29 @@
+
+
0 && statutesEnabled">
+
+ Statute amendment
+
+
+
+
+
+ {{ paragraph.title }}
+
+
+
+
+
{{motion.title}}
+
@@ -283,7 +301,7 @@
The assembly may decide:
-
+
+
+
+
diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss b/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss
index 7e4ac8114..e5a7e6ce3 100644
--- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss
+++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss
@@ -113,6 +113,12 @@ span {
}
}
+.statute-amendment-selector {
+ mat-form-field {
+ margin-left: 20px;
+ }
+}
+
.motion-content {
h4 {
margin: 10px 10px 15px 0;
diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts
index e2c96d5d3..6b26ebfcb 100644
--- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts
+++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts
@@ -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;
+ });
}
}
diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html
index e2adaabe2..ee9632059 100644
--- a/client/src/app/site/motions/components/motion-list/motion-list.component.html
+++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html
@@ -96,7 +96,7 @@
Comment sections
-
+
account_balance
Statute paragraphs
diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.ts b/client/src/app/site/motions/components/motion-list/motion-list.component.ts
index 1e260b8bc..128af41c4 100644
--- a/client/src/app/site/motions/components/motion-list/motion-list.component.ts
+++ b/client/src/app/site/motions/components/motion-list/motion-list.component.ts
@@ -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 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 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 imple
}
});
});
+ this.configService.get('motions_statutes_enabled').subscribe((enabled: boolean): void => {
+ this.statutesEnabled = enabled;
+ });
}
/**
diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts
index 05f07f036..320f429ba 100644
--- a/client/src/app/site/motions/models/view-motion.ts
+++ b/client/src/app/site/motions/models/view-motion.ts
@@ -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
*/
diff --git a/client/src/app/site/motions/services/motion-repository.service.ts b/client/src/app/site/motions/services/motion-repository.service.ts
index afc2fb7b6..7e93c314b 100644
--- a/client/src/app/site/motions/services/motion-repository.service.ts
+++ b/client/src/app/site/motions/services/motion-repository.service.ts
@@ -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
}
}
+ 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
*
diff --git a/openslides/motions/config_variables.py b/openslides/motions/config_variables.py
index 6f4e2a89c..df532a077 100644
--- a/openslides/motions/config_variables.py
+++ b/openslides/motions/config_variables.py
@@ -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',
diff --git a/openslides/motions/serializers.py b/openslides/motions/serializers.py
index c21222ba0..3d1d8f50b 100644
--- a/openslides/motions/serializers.py
+++ b/openslides/motions/serializers.py
@@ -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')