Add amendments to Motion-PDF-Summary-Box
Adds amendments to motion pdf summary box - only if the state of them accepts the merge into the parent motion. Adds a new flatMap function to array.prototype (should be safe to use until Array.flatMap made it into official JS. I expect it in ES 2019. Refactors some PDF and ChangeReco / Amendment related code
This commit is contained in:
parent
a3b5f083d5
commit
bbe966efa9
@ -18,6 +18,16 @@ import { SpinnerService } from './core/ui-services/spinner.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ViewUser } from './site/users/models/view-user';
|
||||
|
||||
/**
|
||||
* Enhance array with own functions
|
||||
* TODO: Remove once flatMap made its way into official JS/TS (ES 2019?)
|
||||
*/
|
||||
declare global {
|
||||
interface Array<T> {
|
||||
flatMap(o: any): Array<any>;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Angular's global App Component
|
||||
*/
|
||||
@ -82,6 +92,7 @@ export class AppComponent {
|
||||
translate.use(translate.getLangs().includes(browserLang) ? browserLang : 'en');
|
||||
// change default JS functions
|
||||
this.overloadArrayToString();
|
||||
this.overloadFlatMap();
|
||||
// Show the spinner initial
|
||||
spinnerService.setVisibility(true, translate.instant('Loading data. Please wait...'));
|
||||
|
||||
@ -148,6 +159,18 @@ export class AppComponent {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an implementation of flatMap.
|
||||
* TODO: Remove once flatMap made its way into official JS/TS (ES 2019?)
|
||||
*/
|
||||
private overloadFlatMap(): void {
|
||||
const concat = (x: any, y: any) => x.concat(y);
|
||||
const flatMap = (f: any, xs: any) => xs.map(f).reduce(concat, []);
|
||||
Array.prototype.flatMap = function(f: any): any[] {
|
||||
return flatMap(f, this);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check if the user is existing and the app is already stable.
|
||||
* If both conditions true, hide the spinner.
|
||||
|
21
client/src/app/shared/utils/recommendation-type-names.ts
Normal file
21
client/src/app/shared/utils/recommendation-type-names.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
|
||||
import { ModificationType } from 'app/core/ui-services/diff.service';
|
||||
|
||||
/**
|
||||
* Gets the name of the modification type
|
||||
*
|
||||
* @param change
|
||||
* @returns the name of a recommendation type
|
||||
*/
|
||||
export function getRecommendationTypeName(change: ViewMotionChangeRecommendation): string {
|
||||
switch (change.type) {
|
||||
case ModificationType.TYPE_REPLACEMENT:
|
||||
return 'Replacement';
|
||||
case ModificationType.TYPE_INSERTION:
|
||||
return 'Insertion';
|
||||
case ModificationType.TYPE_DELETION:
|
||||
return 'Deletion';
|
||||
default:
|
||||
return change.other_description;
|
||||
}
|
||||
}
|
@ -10,6 +10,10 @@ import { MergeAmendment } from 'app/shared/models/motions/workflow-state';
|
||||
* Amendments <-> ViewMotionAmendedParagraph is potentially a 1:n-relation
|
||||
*/
|
||||
export class ViewMotionAmendedParagraph implements ViewUnifiedChange {
|
||||
public get stateName(): string {
|
||||
return this.amendment.state.name;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
private amendment: ViewMotion,
|
||||
private paragraphNo: number,
|
||||
|
@ -18,8 +18,8 @@
|
||||
</span>
|
||||
<span *ngIf="isChangeRecommendation(change)"> ({{ 'Change recommendation' | translate }})</span>
|
||||
<span *ngIf="isAmendment(change)"> ({{ 'Amendment' | translate }} {{ change.getIdentifier() }})</span>
|
||||
<span class="operation" *ngIf="isChangeRecommendation(change)"
|
||||
> – {{ getRecommendationTypeName(change) | translate }}
|
||||
<span class="operation" *ngIf="isChangeRecommendation(change)">
|
||||
– {{ getRecommendationTypeName(change) | translate }}
|
||||
<!--
|
||||
@TODO
|
||||
<span ng-if="change.original.getType(motion.getVersion(version).text) == 3">
|
||||
@ -29,7 +29,9 @@
|
||||
</span>
|
||||
<span class="status">
|
||||
<ng-container *ngIf="change.isRejected()"> – <span translate>Rejected</span></ng-container>
|
||||
<ng-container *ngIf="change.isAccepted() && isAmendment(change)"> – {{ change.amendment.state.name | translate }}</ng-container>
|
||||
<ng-container *ngIf="change.isAccepted() && isAmendment(change)">
|
||||
– {{ change.stateName | translate }}</ng-container
|
||||
>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@ -43,10 +45,11 @@
|
||||
<!-- The actual diff view -->
|
||||
<div class="motion-text-with-diffs">
|
||||
<div *ngFor="let change of changes; let i = index">
|
||||
<div class="motion-text"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
<div
|
||||
class="motion-text"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
>
|
||||
<os-motion-detail-original-change-recommendations
|
||||
[html]="getTextBetweenChanges(changes[i - 1], change)"
|
||||
@ -97,10 +100,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="motion-text"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
<div
|
||||
class="motion-text"
|
||||
[class.line-numbers-none]="isLineNumberingNone()"
|
||||
[class.line-numbers-inline]="isLineNumberingInline()"
|
||||
[class.line-numbers-outside]="isLineNumberingOutside()"
|
||||
>
|
||||
<os-motion-detail-original-change-recommendations
|
||||
[html]="getTextRemainderAfterLastChange()"
|
||||
|
@ -7,7 +7,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
||||
import { DiffService, LineRange, ModificationType } from 'app/core/ui-services/diff.service';
|
||||
import { DiffService, LineRange } from 'app/core/ui-services/diff.service';
|
||||
import {
|
||||
MotionChangeRecommendationComponent,
|
||||
MotionChangeRecommendationComponentData
|
||||
@ -17,6 +17,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ViewMotion, LineNumberingMode } from 'app/site/motions/models/view-motion';
|
||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
||||
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
|
||||
import { getRecommendationTypeName } from 'app/shared/utils/recommendation-type-names';
|
||||
|
||||
/**
|
||||
* This component displays the original motion text with the change blocks inside.
|
||||
@ -45,6 +46,11 @@ import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-mot
|
||||
styleUrls: ['./motion-detail-diff.component.scss']
|
||||
})
|
||||
export class MotionDetailDiffComponent extends BaseViewComponent implements AfterViewInit {
|
||||
/**
|
||||
* Get the {@link getRecommendationTypeName}-Function from Utils
|
||||
*/
|
||||
public getRecommendationTypeName = getRecommendationTypeName;
|
||||
|
||||
@Input()
|
||||
public motion: ViewMotion;
|
||||
@Input()
|
||||
@ -222,24 +228,6 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
return change.getChangeType() === ViewUnifiedChangeType.TYPE_CHANGE_RECOMMENDATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the modification type
|
||||
*
|
||||
* @param change
|
||||
*/
|
||||
public getRecommendationTypeName(change: ViewMotionChangeRecommendation): string {
|
||||
switch (change.type) {
|
||||
case ModificationType.TYPE_REPLACEMENT:
|
||||
return 'Replacement';
|
||||
case ModificationType.TYPE_INSERTION:
|
||||
return 'Insertion';
|
||||
case ModificationType.TYPE_DELETION:
|
||||
return 'Deletion';
|
||||
default:
|
||||
return '@UNKNOWN@';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a change recommendation to accepted or rejected.
|
||||
* The template has to make sure only to pass change recommendations to this method.
|
||||
|
@ -5,6 +5,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { CalculablePollKey } from 'app/core/ui-services/poll.service';
|
||||
import { ChangeRecommendationRepositoryService } from 'app/core/repositories/motions/change-recommendation-repository.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { getRecommendationTypeName } from 'app/shared/utils/recommendation-type-names';
|
||||
import { HtmlToPdfService } from 'app/core/ui-services/html-to-pdf.service';
|
||||
import { MotionPollService } from './motion-poll.service';
|
||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
@ -12,8 +13,10 @@ import { StatuteParagraphRepositoryService } from 'app/core/repositories/motions
|
||||
import { ViewMotion, LineNumberingMode, ChangeRecoMode } from '../models/view-motion';
|
||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||
import { MotionCommentSectionRepositoryService } from 'app/core/repositories/motions/motion-comment-section-repository.service';
|
||||
import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
|
||||
import { PdfDocumentService } from 'app/core/ui-services/pdf-document.service';
|
||||
import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph';
|
||||
import { ViewMotionChangeRecommendation } from '../models/view-motion-change-recommendation';
|
||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from 'app/shared/models/motions/view-unified-change';
|
||||
|
||||
/**
|
||||
* Type declaring which strings are valid options for metainfos to be exported into a pdf
|
||||
@ -45,6 +48,11 @@ export type InfoToExport =
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MotionPdfService {
|
||||
/**
|
||||
* Get the {@link getRecommendationTypeName}-Function from Utils
|
||||
*/
|
||||
public getRecommendationTypeName = getRecommendationTypeName;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -91,6 +99,8 @@ export class MotionPdfService {
|
||||
infoToExport?: InfoToExport[],
|
||||
commentsToExport?: number[]
|
||||
): object {
|
||||
// get the line length from the config
|
||||
const lineLength = this.configService.instant<number>('motions_line_length');
|
||||
let motionPdfContent = [];
|
||||
|
||||
// Enforces that statutes should always have Diff Mode and no line numbers
|
||||
@ -116,14 +126,14 @@ export class MotionPdfService {
|
||||
motionPdfContent = [title, subtitle];
|
||||
|
||||
if ((infoToExport && infoToExport.length > 0) || !infoToExport) {
|
||||
const metaInfo = this.createMetaInfoTable(motion, crMode, infoToExport);
|
||||
const metaInfo = this.createMetaInfoTable(motion, lineLength, crMode, infoToExport);
|
||||
motionPdfContent.push(metaInfo);
|
||||
}
|
||||
|
||||
if (!contentToExport || contentToExport.includes('text')) {
|
||||
const preamble = this.createPreamble(motion);
|
||||
motionPdfContent.push(preamble);
|
||||
const text = this.createText(motion, lnMode, crMode);
|
||||
const text = this.createText(motion, lineLength, lnMode, crMode);
|
||||
motionPdfContent.push(text);
|
||||
}
|
||||
|
||||
@ -192,7 +202,12 @@ export class MotionPdfService {
|
||||
* @param motion the target motion
|
||||
* @returns doc def for the meta infos
|
||||
*/
|
||||
private createMetaInfoTable(motion: ViewMotion, crMode: ChangeRecoMode, infoToExport?: InfoToExport[]): object {
|
||||
private createMetaInfoTable(
|
||||
motion: ViewMotion,
|
||||
lineLength: number,
|
||||
crMode: ChangeRecoMode,
|
||||
infoToExport?: InfoToExport[]
|
||||
): object {
|
||||
const metaTableBody = [];
|
||||
|
||||
// submitters
|
||||
@ -367,23 +382,13 @@ export class MotionPdfService {
|
||||
}
|
||||
|
||||
// summary of change recommendations (for motion diff version only)
|
||||
const changeRecos = this.changeRecoRepo
|
||||
.getChangeRecoOfMotion(motion.id)
|
||||
.sort((a: ViewUnifiedChange, b: ViewUnifiedChange) => {
|
||||
if (a.getLineFrom() < b.getLineFrom()) {
|
||||
return -1;
|
||||
} else if (a.getLineFrom() > b.getLineFrom()) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
const changes = this.getUnifiedChanges(motion, lineLength);
|
||||
|
||||
if (crMode === ChangeRecoMode.Diff && changeRecos.length > 0) {
|
||||
if (crMode === ChangeRecoMode.Diff && changes.length > 0) {
|
||||
const columnLineNumbers = [];
|
||||
const columnChangeType = [];
|
||||
|
||||
changeRecos.forEach(changeReco => {
|
||||
changes.forEach(change => {
|
||||
// TODO: the function isTitleRecommendation() does not exist anymore.
|
||||
// Not sure if required or not
|
||||
// if (changeReco.isTitleRecommendation()) {
|
||||
@ -392,44 +397,56 @@ export class MotionPdfService {
|
||||
|
||||
// line numbers column
|
||||
let line;
|
||||
if (changeReco.line_from >= changeReco.line_to - 1) {
|
||||
line = changeReco.line_from;
|
||||
if (change.getLineFrom() >= change.getLineTo() - 1) {
|
||||
line = change.getLineFrom();
|
||||
} else {
|
||||
line = changeReco.line_from + ' - ' + (changeReco.line_to - 1);
|
||||
line = change.getLineFrom() + ' - ' + (change.getLineTo() - 1);
|
||||
}
|
||||
columnLineNumbers.push(`${this.translate.instant('Line')} ${line}: `);
|
||||
|
||||
// change type column
|
||||
if (changeReco.type === 0) {
|
||||
columnChangeType.push(this.translate.instant('Replacement'));
|
||||
} else if (changeReco.type === 1) {
|
||||
columnChangeType.push(this.translate.instant('Insertion'));
|
||||
} else if (changeReco.type === 2) {
|
||||
columnChangeType.push(this.translate.instant('Deletion'));
|
||||
} else if (changeReco.type === 3) {
|
||||
columnChangeType.push(changeReco.other_description);
|
||||
if (change.getChangeType() === ViewUnifiedChangeType.TYPE_CHANGE_RECOMMENDATION) {
|
||||
const changeReco = change as ViewMotionChangeRecommendation;
|
||||
columnLineNumbers.push(`${this.translate.instant('Line')} ${line}: `);
|
||||
columnChangeType.push(
|
||||
`(${this.translate.instant('Change recommendation')}) - ${this.translate.instant(
|
||||
this.getRecommendationTypeName(changeReco)
|
||||
)}`
|
||||
);
|
||||
} else if (change.getChangeType() === ViewUnifiedChangeType.TYPE_AMENDMENT) {
|
||||
const amendment = change as ViewMotionAmendedParagraph;
|
||||
let summaryText = `(${this.translate.instant('Amendment')} ${amendment.getIdentifier()}) -`;
|
||||
if (amendment.isRejected()) {
|
||||
summaryText += ` ${this.translate.instant('Rejected')}`;
|
||||
} else if (amendment.isAccepted()) {
|
||||
summaryText += ` ${this.translate.instant(amendment.stateName)}`;
|
||||
// only append line and change, if the merge of the state of the amendment is accepted.
|
||||
columnLineNumbers.push(`${this.translate.instant('Line')} ${line}: `);
|
||||
columnChangeType.push(summaryText);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
metaTableBody.push([
|
||||
{
|
||||
text: this.translate.instant('Summary of changes'),
|
||||
style: 'boldText'
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
text: columnLineNumbers.join('\n'),
|
||||
width: 'auto'
|
||||
},
|
||||
{
|
||||
text: columnChangeType.join('\n'),
|
||||
width: 'auto'
|
||||
}
|
||||
],
|
||||
columnGap: 7
|
||||
}
|
||||
]);
|
||||
if (columnChangeType.length > 0) {
|
||||
metaTableBody.push([
|
||||
{
|
||||
text: this.translate.instant('Summary of changes'),
|
||||
style: 'boldText'
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
text: columnLineNumbers.join('\n'),
|
||||
width: 'auto'
|
||||
},
|
||||
{
|
||||
text: columnChangeType.join('\n'),
|
||||
width: 'auto'
|
||||
}
|
||||
],
|
||||
columnGap: 7
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (metaTableBody.length > 0) {
|
||||
@ -479,10 +496,13 @@ export class MotionPdfService {
|
||||
* @param crMode determine the used change Recommendation mode
|
||||
* @returns doc def for the "the assembly may decide" preamble
|
||||
*/
|
||||
private createText(motion: ViewMotion, lnMode: LineNumberingMode, crMode: ChangeRecoMode): object {
|
||||
private createText(
|
||||
motion: ViewMotion,
|
||||
lineLength: number,
|
||||
lnMode: LineNumberingMode,
|
||||
crMode: ChangeRecoMode
|
||||
): object {
|
||||
let motionText: string;
|
||||
// get the line length from the config
|
||||
const lineLength = this.configService.instant<number>('motions_line_length');
|
||||
|
||||
if (motion.isParagraphBasedAmendment()) {
|
||||
motionText = '';
|
||||
@ -510,26 +530,8 @@ export class MotionPdfService {
|
||||
} else {
|
||||
// lead motion or normal amendments
|
||||
// TODO: Consider tile change recommendation
|
||||
const changes: ViewUnifiedChange[] = Object.assign(
|
||||
[],
|
||||
this.changeRecoRepo.getChangeRecoOfMotion(motion.id)
|
||||
);
|
||||
|
||||
// TODO: Cleanup, everything change reco and amendment based needs a unified structure.
|
||||
const amendments = this.motionRepo.getAmendmentsInstantly(motion.id);
|
||||
if (amendments) {
|
||||
for (const amendment of amendments) {
|
||||
const changedParagraphs = this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength);
|
||||
for (const change of changedParagraphs) {
|
||||
changes.push(change as ViewUnifiedChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// changes need to be sorted, by "line from".
|
||||
// otherwise, formatMotion will make unexpected results by messing up the
|
||||
// order of changes applied to the motion
|
||||
changes.sort((a, b) => a.getLineFrom() - b.getLineFrom());
|
||||
const changes = this.getUnifiedChanges(motion, lineLength);
|
||||
motionText = this.motionRepo.formatMotion(motion.id, crMode, changes, lineLength);
|
||||
// reformat motion text to split long HTML elements to easier convert into PDF
|
||||
motionText = this.linenumberingService.splitInlineElementsAtLineBreaks(motionText);
|
||||
@ -538,6 +540,30 @@ export class MotionPdfService {
|
||||
return this.htmlToPdfService.convertHtml(motionText, lnMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* changes need to be sorted, by "line from".
|
||||
* otherwise, formatMotion will make unexpected results by messing up the
|
||||
* order of changes applied to the motion
|
||||
*
|
||||
* TODO: Cleanup, everything change reco and amendment based needs a unified structure.
|
||||
*
|
||||
* @param motion
|
||||
* @param lineLength
|
||||
* @returns
|
||||
*/
|
||||
private getUnifiedChanges(motion: ViewMotion, lineLength: number): ViewUnifiedChange[] {
|
||||
return this.changeRecoRepo
|
||||
.getChangeRecoOfMotion(motion.id)
|
||||
.concat(
|
||||
this.motionRepo
|
||||
.getAmendmentsInstantly(motion.id)
|
||||
.flatMap((amendment: ViewMotion) =>
|
||||
this.motionRepo.getAmendmentAmendedParagraphs(amendment, lineLength)
|
||||
)
|
||||
)
|
||||
.sort((a, b) => a.getLineFrom() - b.getLineFrom()) as ViewUnifiedChange[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the motion reason - uses HTML to PDF
|
||||
*
|
||||
@ -688,6 +714,7 @@ export class MotionPdfService {
|
||||
const subtitle = this.createSubtitle(motion);
|
||||
const metaInfo = this.createMetaInfoTable(
|
||||
motion,
|
||||
this.configService.instant<number>('motions_line_length'),
|
||||
this.configService.instant('motions_recommendation_text_mode'),
|
||||
['submitters', 'state', 'category']
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user