Merge pull request #4994 from tsiegleauq/amendment-list-pdf-overview
Add amendment overview list as PDF
This commit is contained in:
commit
39b0168714
@ -138,6 +138,12 @@ const MotionRelations: RelationDefinition[] = [
|
||||
ownIdKey: 'change_recommendations_id',
|
||||
ownKey: 'changeRecommendations',
|
||||
foreignViewModel: ViewMotionChangeRecommendation
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
foreignIdKey: 'parent_id',
|
||||
ownKey: 'amendments',
|
||||
foreignViewModel: ViewMotion
|
||||
}
|
||||
// Personal notes are dynamically added in the repo.
|
||||
];
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { OverlayService } from './overlay.service';
|
||||
|
||||
describe('OverlayService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: OverlayService = TestBed.get(OverlayService);
|
||||
|
@ -4,7 +4,7 @@ import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { PreviewComponent } from './preview.component';
|
||||
|
||||
fdescribe('PreviewComponent', () => {
|
||||
describe('PreviewComponent', () => {
|
||||
let component: PreviewComponent;
|
||||
let fixture: ComponentFixture<PreviewComponent>;
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { ProgressSnackBarComponent } from './progress-snack-bar.component';
|
||||
|
||||
fdescribe('ProgressSnackBarComponent', () => {
|
||||
describe('ProgressSnackBarComponent', () => {
|
||||
let component: ProgressSnackBarComponent;
|
||||
let fixture: ComponentFixture<ProgressSnackBarComponent>;
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { async, TestBed } from '@angular/core/testing';
|
||||
// import { SuperSearchComponent } from './super-search.component';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
fdescribe('SuperSearchComponent', () => {
|
||||
describe('SuperSearchComponent', () => {
|
||||
// let component: SuperSearchComponent;
|
||||
// let fixture: ComponentFixture<SuperSearchComponent>;
|
||||
|
||||
|
@ -62,24 +62,6 @@ export interface MotionTitleInformation extends TitleInformationWithAgendaItem {
|
||||
*/
|
||||
export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Motion>
|
||||
implements MotionTitleInformation, Searchable {
|
||||
public static COLLECTIONSTRING = Motion.COLLECTIONSTRING;
|
||||
protected _collectionString = Motion.COLLECTIONSTRING;
|
||||
|
||||
protected _category?: ViewCategory;
|
||||
protected _submitters?: ViewSubmitter[];
|
||||
protected _supporters?: ViewUser[];
|
||||
protected _workflow?: ViewWorkflow;
|
||||
protected _state?: ViewState;
|
||||
protected _recommendation?: ViewState;
|
||||
protected _motion_block?: ViewMotionBlock;
|
||||
protected _attachments?: ViewMediafile[];
|
||||
protected _tags?: ViewTag[];
|
||||
protected _parent?: ViewMotion;
|
||||
protected _amendments?: ViewMotion[];
|
||||
protected _changeRecommendations?: ViewMotionChangeRecommendation[];
|
||||
protected _diffLines?: DiffLinesInParagraph[];
|
||||
public personalNote?: PersonalNoteContent;
|
||||
|
||||
public get motion(): Motion {
|
||||
return this._model;
|
||||
}
|
||||
@ -368,10 +350,48 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static COLLECTIONSTRING = Motion.COLLECTIONSTRING;
|
||||
protected _collectionString = Motion.COLLECTIONSTRING;
|
||||
|
||||
protected _category?: ViewCategory;
|
||||
protected _submitters?: ViewSubmitter[];
|
||||
protected _supporters?: ViewUser[];
|
||||
protected _workflow?: ViewWorkflow;
|
||||
protected _state?: ViewState;
|
||||
protected _recommendation?: ViewState;
|
||||
protected _motion_block?: ViewMotionBlock;
|
||||
protected _attachments?: ViewMediafile[];
|
||||
protected _tags?: ViewTag[];
|
||||
protected _parent?: ViewMotion;
|
||||
protected _amendments?: ViewMotion[];
|
||||
protected _changeRecommendations?: ViewMotionChangeRecommendation[];
|
||||
protected _diffLines?: DiffLinesInParagraph[];
|
||||
public personalNote?: PersonalNoteContent;
|
||||
|
||||
// This is set by the repository
|
||||
public getIdentifierOrTitle: () => string;
|
||||
|
||||
/**
|
||||
* Extract the lines of the amendments
|
||||
* If an amendments has multiple changes, they will be printed like an array of strings
|
||||
*
|
||||
* @param amendment the motion to create the amendment to
|
||||
* @return The lines of the amendment
|
||||
*/
|
||||
public getChangeLines(): string {
|
||||
if (!!this.diffLines) {
|
||||
return this.diffLines
|
||||
.map(diffLine => {
|
||||
if (diffLine.diffLineTo === diffLine.diffLineFrom + 1) {
|
||||
return '' + diffLine.diffLineFrom;
|
||||
} else {
|
||||
return `${diffLine.diffLineFrom} - ${diffLine.diffLineTo - 1}`;
|
||||
}
|
||||
})
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the category for search
|
||||
*
|
||||
|
@ -46,7 +46,7 @@
|
||||
<span *ngIf="motion.diffLines && motion.diffLines.length">
|
||||
<span *ngIf="motion.identifier"> · </span>
|
||||
<span translate>Line</span>
|
||||
<span> {{ getChangeLines(motion) }}</span>
|
||||
<span> {{ motion.getChangeLines() }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -106,6 +106,10 @@
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="exportAmendmentListPdf()">
|
||||
<mat-icon>picture_as_pdf</mat-icon>
|
||||
<span translate>Amendment list PDF</span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="isMultiSelect">
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
|
@ -18,6 +18,7 @@ import { largeDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
||||
import { MotionExportDialogComponent } from '../shared-motion/motion-export-dialog/motion-export-dialog.component';
|
||||
import { MotionExportInfo, MotionExportService } from '../../services/motion-export.service';
|
||||
import { MotionPdfExportService } from '../../services/motion-pdf-export.service';
|
||||
import { MotionSortListService } from '../../services/motion-sort-list.service';
|
||||
import { ViewMotion } from '../../models/view-motion';
|
||||
|
||||
@ -32,6 +33,11 @@ import { ViewMotion } from '../../models/view-motion';
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> implements OnInit {
|
||||
/**
|
||||
* Hold the Id of the parent motion
|
||||
*/
|
||||
private parentMotionId: number;
|
||||
|
||||
/**
|
||||
* Hold the parent motion if present
|
||||
*/
|
||||
@ -91,7 +97,8 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
|
||||
private sanitizer: DomSanitizer,
|
||||
private dialog: MatDialog,
|
||||
private motionExport: MotionExportService,
|
||||
private linenumberingService: LinenumberingService
|
||||
private linenumberingService: LinenumberingService,
|
||||
private pdfExport: MotionPdfExportService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar, storage);
|
||||
super.setTitle('Amendments');
|
||||
@ -105,9 +112,9 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
|
||||
// if there is a subscription to the parent motion
|
||||
this.parentMotion = this.route.paramMap.pipe(
|
||||
switchMap((params: ParamMap) => {
|
||||
const parentMotionId = +params.get('id');
|
||||
this.amendmentFilterService.parentMotionId = parentMotionId;
|
||||
return this.motionRepo.getViewModelObservable(parentMotionId);
|
||||
this.parentMotionId = +params.get('id');
|
||||
this.amendmentFilterService.parentMotionId = this.parentMotionId;
|
||||
return this.motionRepo.getViewModelObservable(this.parentMotionId);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
@ -115,29 +122,6 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the lines of the amendments
|
||||
* If an amendments has multiple changes, they will be printed like an array of strings
|
||||
*
|
||||
* @param amendment the motion to create the amendment to
|
||||
* @return The lines of the amendment
|
||||
*/
|
||||
public getChangeLines(amendment: ViewMotion): string {
|
||||
const diffLines = amendment.diffLines;
|
||||
|
||||
if (!!diffLines) {
|
||||
return diffLines
|
||||
.map(diffLine => {
|
||||
if (diffLine.diffLineTo === diffLine.diffLineFrom + 1) {
|
||||
return '' + diffLine.diffLineFrom;
|
||||
} else {
|
||||
return `${diffLine.diffLineFrom} - ${diffLine.diffLineTo - 1}`;
|
||||
}
|
||||
})
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formulate the amendment summary
|
||||
*
|
||||
@ -172,6 +156,14 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the given motion ist as special PDF
|
||||
*/
|
||||
public exportAmendmentListPdf(): void {
|
||||
const parentMotion = this.parentMotionId ? this.motionRepo.getViewModel(this.parentMotionId) : undefined;
|
||||
this.pdfExport.exportAmendmentList(this.dataSource.filteredData, parentMotion);
|
||||
}
|
||||
|
||||
public sanitizeText(text: string): SafeHtml {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(text);
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { AmendmentListPdfService } from './amendment-list-pdf.service';
|
||||
|
||||
describe('AmendmentListPdfService', () => {
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AmendmentListPdfService = TestBed.get(AmendmentListPdfService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,121 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { HtmlToPdfService } from 'app/core/pdf-services/html-to-pdf.service';
|
||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
||||
import { ViewMotion } from '../models/view-motion';
|
||||
|
||||
/**
|
||||
* Creates a PDF list for amendments
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AmendmentListPdfService {
|
||||
public constructor(
|
||||
private motionRepo: MotionRepositoryService,
|
||||
private translate: TranslateService,
|
||||
private htmlToPdfService: HtmlToPdfService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Also required by amendment-detail. Should be own service
|
||||
* @param amendment
|
||||
* @return rendered PDF text
|
||||
*/
|
||||
private renderDiffLines(amendment: ViewMotion): object {
|
||||
if (amendment.diffLines && amendment.diffLines.length) {
|
||||
const linesHtml = amendment.diffLines.map(line => line.text).join('<br />[...]<br />');
|
||||
return this.htmlToPdfService.convertHtml(linesHtml);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an amendment to a row of the `amendmentRows` table
|
||||
* @amendment the amendment to convert
|
||||
* @returns a line in the row as PDF doc definition
|
||||
*/
|
||||
private amendmentToTableRow(amendment: ViewMotion): object {
|
||||
let recommendationText = '';
|
||||
if (amendment.recommendation) {
|
||||
if (amendment.recommendation.show_recommendation_extension_field && amendment.recommendationExtension) {
|
||||
recommendationText += ` ${this.motionRepo.getExtendedRecommendationLabel(amendment)}`;
|
||||
} else {
|
||||
recommendationText += this.translate.instant(amendment.recommendation.recommendation_label);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
text: amendment.identifierOrTitle
|
||||
},
|
||||
{
|
||||
text: amendment.getChangeLines()
|
||||
},
|
||||
{
|
||||
text: amendment.submittersAsUsers.toString()
|
||||
},
|
||||
{
|
||||
text: this.renderDiffLines(amendment)
|
||||
},
|
||||
{
|
||||
text: recommendationText
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the PDFmake document structure for amendment list overview
|
||||
* @param docTitle the header
|
||||
* @param amendments the amendments to render
|
||||
*/
|
||||
public overviewToDocDef(docTitle: string, amendments: ViewMotion[]): object {
|
||||
const title = {
|
||||
text: docTitle,
|
||||
style: 'title'
|
||||
};
|
||||
|
||||
const amendmentTableBody: object[] = [
|
||||
[
|
||||
{
|
||||
text: this.translate.instant('Motion'),
|
||||
style: 'tableHeader'
|
||||
},
|
||||
{
|
||||
text: this.translate.instant('Line'),
|
||||
style: 'tableHeader'
|
||||
},
|
||||
{
|
||||
text: this.translate.instant('Submitters'),
|
||||
style: 'tableHeader'
|
||||
},
|
||||
{
|
||||
text: this.translate.instant('Changes'),
|
||||
style: 'tableHeader'
|
||||
},
|
||||
{
|
||||
text: this.translate.instant('Recommendation'),
|
||||
style: 'tableHeader'
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
const amendmentRows: object[] = [];
|
||||
for (const amendment of amendments) {
|
||||
amendmentRows.push(this.amendmentToTableRow(amendment));
|
||||
}
|
||||
|
||||
const table: object = {
|
||||
table: {
|
||||
widths: ['auto', 'auto', 'auto', '*', 'auto'],
|
||||
headerRows: 1,
|
||||
dontBreakRows: true,
|
||||
body: amendmentTableBody.concat(amendmentRows)
|
||||
},
|
||||
layout: 'switchColorTableLayout'
|
||||
};
|
||||
|
||||
return [title, table];
|
||||
}
|
||||
}
|
@ -1,9 +1,15 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { AmendmentSortListService } from './amendment-sort-list.service';
|
||||
|
||||
describe('AmendmentSortListService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
beforeEach(() =>
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
})
|
||||
);
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AmendmentSortListService = TestBed.get(AmendmentSortListService);
|
||||
|
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { AmendmentListPdfService } from './amendment-list-pdf.service';
|
||||
import { PdfDocumentService } from 'app/core/pdf-services/pdf-document.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
|
||||
@ -30,6 +31,7 @@ export class MotionPdfExportService {
|
||||
private translate: TranslateService,
|
||||
private configService: ConfigService,
|
||||
private motionPdfService: MotionPdfService,
|
||||
private amendmentListPdfService: AmendmentListPdfService,
|
||||
private pdfCatalogService: MotionPdfCatalogService,
|
||||
private pdfDocumentService: PdfDocumentService
|
||||
) {}
|
||||
@ -116,4 +118,22 @@ export class MotionPdfExportService {
|
||||
this.pdfDocumentService.download(doc, filename, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the amendments to the given motion as an overview table
|
||||
* @param parentMotion
|
||||
*/
|
||||
public exportAmendmentList(amendments: ViewMotion[], parentMotion?: ViewMotion): void {
|
||||
let filename: string;
|
||||
if (parentMotion) {
|
||||
filename = `${this.translate.instant('Amendments to')} ${parentMotion.getListTitle()}`;
|
||||
} else {
|
||||
filename = `${this.translate.instant('Amendments')}`;
|
||||
}
|
||||
const doc = this.amendmentListPdfService.overviewToDocDef(filename, amendments);
|
||||
const metadata = {
|
||||
title: filename
|
||||
};
|
||||
this.pdfDocumentService.downloadLandscape(doc, filename, metadata);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user