243 lines
7.9 KiB
TypeScript
243 lines
7.9 KiB
TypeScript
import { ConfigService } from 'app/core/ui-services/config.service';
|
|
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
|
|
|
/**
|
|
* Server side ballot choice definitions.
|
|
* Server-defined methods to determine the number of ballots to print
|
|
* Options are:
|
|
* - NUMBER_OF_DELEGATES Amount of users belonging to the predefined 'delegates' group (group id 2)
|
|
* - NUMBER_OF_ALL_PARTICIPANTS The amount of all registered users
|
|
* - CUSTOM_NUMBER a given number of ballots (see {@link ballotCustomCount})
|
|
*/
|
|
export type BallotCountChoices = 'NUMBER_OF_DELEGATES' | 'NUMBER_OF_ALL_PARTICIPANTS' | 'CUSTOM_NUMBER';
|
|
|
|
/**
|
|
* Workaround data definitions. The implementation for the different model's classes might have different needs,
|
|
* so some data might not be required.
|
|
*
|
|
*/
|
|
export interface AbstractPollData {
|
|
title: string;
|
|
subtitle?: string;
|
|
sheetend: number; // should reflect the vertical size of one ballot on the paper
|
|
poll?: ViewAssignmentPoll; // TODO ugly workaround because assignment poll needs the poll on ballot level
|
|
}
|
|
|
|
export abstract class PollPdfService {
|
|
/**
|
|
* Definition of method to decide which amount of ballots to print. The implementations
|
|
* are expected to fetch this information from the configuration service
|
|
* @see BallotCountChoices
|
|
*/
|
|
protected ballotCountSelection: BallotCountChoices;
|
|
|
|
/**
|
|
* An arbitrary number of ballots to print, if {@link ballotCountSelection} is set
|
|
* to CUSTOM_NUMBER. Value is expected to be fetched from the configuration`
|
|
*/
|
|
protected ballotCustomCount: number;
|
|
|
|
/**
|
|
* The event name (as set in config `general_event_name`)
|
|
*/
|
|
protected eventName: string;
|
|
|
|
/**
|
|
* The url of the logo to be printed (as set in config `logo_pdf_ballot_paper`)
|
|
*/
|
|
protected logo: string;
|
|
|
|
/**
|
|
* Contructor. Subscribes to the logo path and event name
|
|
* @param configService Configzuration
|
|
* @param userRepo user Repository for determining the number of ballots
|
|
*/
|
|
public constructor(protected configService: ConfigService, protected userRepo: UserRepositoryService) {
|
|
this.configService.get<string>('general_event_name').subscribe(name => (this.eventName = name));
|
|
this.configService.get<{ path?: string }>('logo_pdf_ballot_paper').subscribe(url => {
|
|
if (url && url.path) {
|
|
if (url.path.indexOf('/') === 0) {
|
|
url.path = url.path.substr(1); // remove prepending slash
|
|
}
|
|
this.logo = url.path;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the amount of ballots to be printed
|
|
*
|
|
* @returns the amount of ballots, depending on the config settings
|
|
*/
|
|
protected getBallotCount(): number {
|
|
switch (this.ballotCountSelection) {
|
|
case 'NUMBER_OF_ALL_PARTICIPANTS':
|
|
return this.userRepo.getViewModelList().length;
|
|
case 'NUMBER_OF_DELEGATES':
|
|
return this.userRepo.getViewModelList().filter(user => user.groups_id && user.groups_id.includes(2))
|
|
.length;
|
|
case 'CUSTOM_NUMBER':
|
|
return this.ballotCustomCount;
|
|
default:
|
|
throw new Error('Amount of ballots cannot be computed');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an entry for an option (a label with a circle)
|
|
*
|
|
* @returns pdfMake definitions
|
|
*/
|
|
protected createBallotOption(decision: string): { margin: number[]; columns: object[] } {
|
|
const BallotCircleDimensions = { yDistance: 6, size: 8 };
|
|
return {
|
|
margin: [40 + BallotCircleDimensions.size, 10, 0, 0],
|
|
columns: [
|
|
{
|
|
width: 15,
|
|
canvas: this.drawCircle(BallotCircleDimensions.yDistance, BallotCircleDimensions.size)
|
|
},
|
|
{
|
|
width: 'auto',
|
|
text: decision
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Helper to draw a circle on its position on the ballot paper
|
|
*
|
|
* @param y vertical offset
|
|
* @param size the size of the circle
|
|
* @returns an array containing one circle definition for pdfMake
|
|
*/
|
|
private drawCircle(y: number, size: number): object[] {
|
|
return [
|
|
{
|
|
type: 'ellipse',
|
|
x: 0,
|
|
y: y,
|
|
lineColor: 'black',
|
|
r1: size,
|
|
r2: size
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Abstract function for creating a single ballot with header and all options
|
|
*
|
|
* @param data AbstractPollData
|
|
* @returns pdfmake definitions
|
|
*/
|
|
protected abstract createBallot(data: AbstractPollData): object;
|
|
|
|
/**
|
|
* Create a createPdf definition for the correct amount of ballots
|
|
*
|
|
* @param rowsPerPage (precalculated) value of pair of ballots fitting on one page.
|
|
* A value too high might result in phantom items split onto several pages
|
|
* @param data predefined data to be used
|
|
* @returns pdfmake definitions
|
|
*/
|
|
protected getPages(rowsPerPage: number, data: AbstractPollData): object {
|
|
const amount = this.getBallotCount();
|
|
const fullpages = Math.floor(amount / (rowsPerPage * 2));
|
|
let partialpageEntries = amount % (rowsPerPage * 2);
|
|
const content: object[] = [];
|
|
for (let i = 0; i < fullpages; i++) {
|
|
const body = [];
|
|
for (let j = 0; j < rowsPerPage; j++) {
|
|
body.push([this.createBallot(data), this.createBallot(data)]);
|
|
}
|
|
content.push({
|
|
table: {
|
|
headerRows: 1,
|
|
widths: ['*', '*'],
|
|
body: body,
|
|
pageBreak: 'after'
|
|
},
|
|
rowsperpage: rowsPerPage
|
|
});
|
|
}
|
|
if (partialpageEntries) {
|
|
const partialPageBody = [];
|
|
while (partialpageEntries > 1) {
|
|
partialPageBody.push([this.createBallot(data), this.createBallot(data)]);
|
|
partialpageEntries -= 2;
|
|
}
|
|
if (partialpageEntries === 1) {
|
|
partialPageBody.push([this.createBallot(data), '']);
|
|
}
|
|
content.push({
|
|
table: {
|
|
headerRows: 1,
|
|
widths: ['50%', '50%'],
|
|
body: partialPageBody
|
|
},
|
|
rowsperpage: rowsPerPage
|
|
});
|
|
}
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* get a pdfMake header definition with the event name and an optional logo
|
|
*
|
|
* @returns pdfMake definitions
|
|
*/
|
|
protected getHeader(): object {
|
|
const columns: object[] = [];
|
|
columns.push({
|
|
text: this.eventName,
|
|
fontSize: 8,
|
|
alignment: 'left',
|
|
width: '60%'
|
|
});
|
|
|
|
if (this.logo) {
|
|
columns.push({
|
|
image: this.logo,
|
|
fit: [90, 25],
|
|
alignment: 'right',
|
|
width: '40%'
|
|
});
|
|
}
|
|
return {
|
|
color: '#555',
|
|
fontSize: 10,
|
|
margin: [30, 10, 10, -10], // [left, top, right, bottom]
|
|
columns: columns,
|
|
columnGap: 5
|
|
};
|
|
}
|
|
|
|
/**
|
|
* create a pdfmake definition for a title entry
|
|
*
|
|
* @param title
|
|
* @returns pdfmake definition
|
|
*/
|
|
protected getTitle(title: string): object {
|
|
return {
|
|
text: title,
|
|
style: 'title'
|
|
};
|
|
}
|
|
|
|
/**
|
|
* create a pdfmake definition for a subtitle entry
|
|
*
|
|
* @param subtitle
|
|
* @returns pdfmake definition
|
|
*/
|
|
protected getSubtitle(subtitle: string): object {
|
|
return {
|
|
text: subtitle,
|
|
style: 'description'
|
|
};
|
|
}
|
|
}
|