#2-protocol #10

Merged
jkeffects merged 14 commits from #2-protocol into main 2024-10-29 14:45:37 +00:00
12 changed files with 341 additions and 54 deletions
Showing only changes of commit 9da2a98f55 - Show all commits

View file

@ -0,0 +1,6 @@
export interface CreateProtocolPrintoutCommand {
title: string;
iteration: number;
filename: string;
protocolId: number;
}

View file

@ -0,0 +1,31 @@
import { dataSource } from "../data-source";
import { protocolPrintout } from "../entity/protocolPrintout";
import InternalException from "../exceptions/internalException";
import { CreateProtocolPrintoutCommand } from "./protocolPrintoutCommand";
export default abstract class ProtocolPrintoutCommandHandler {
/**
* @description create protocolPrintout
* @param {number}
* @returns {Promise<number>}
*/
static async create(printout: CreateProtocolPrintoutCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(protocolPrintout)
.values({
title: printout.title,
iteration: printout.iteration,
filename: printout.filename,
protocolId: printout.protocolId,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new InternalException("Failed creating protocol", err);
});
}
}

View file

@ -23,6 +23,10 @@ import { SynchronizeProtocolVotingCommand } from "../../command/protocolVotingCo
import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models";
import ProtocolVotingCommandHandler from "../../command/protocolVotingCommandHandler";
import { PdfExport } from "../../helpers/pdfExport";
import ProtocolPrintoutService from "../../service/protocolPrintoutService";
import ProtocolPrintoutFactory from "../../factory/admin/protocolPrintout";
import { CreateProtocolPrintoutCommand } from "../../command/protocolPrintoutCommand";
import ProtocolPrintoutCommandHandler from "../../command/protocolPrintoutCommandHandler";
/**
* @description get all protocols
@ -112,6 +116,35 @@ export async function getProtocolVotingsById(req: Request, res: Response): Promi
res.json(ProtocolVotingFactory.mapToBase(votings));
}
/**
* @description get protocol printouts by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getProtocolPrintoutsById(req: Request, res: Response): Promise<any> {
let protocolId = parseInt(req.params.protocolId);
let printouts = await ProtocolPrintoutService.getAll(protocolId);
res.json(ProtocolPrintoutFactory.mapToBase(printouts));
}
/**
* @description get protocol printout by id and print
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getProtocolPrintoutByIdAndPrint(req: Request, res: Response): Promise<any> {
let protocolId = parseInt(req.params.protocolId);
let printoutId = parseInt(req.params.printoutId);
let printouts = await ProtocolPrintoutService.getById(printoutId, protocolId);
res.json(ProtocolPrintoutFactory.mapToSingle(printouts));
}
/**
* @description create protocol
* @param req {Request} Express req object
@ -173,6 +206,69 @@ export async function createProtocolVotingsById(req: Request, res: Response): Pr
res.send(voting);
}
/**
* @description create protocol printout by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function createProtocolPrintoutById(req: Request, res: Response): Promise<any> {
let protocolId = parseInt(req.params.protocolId);
let protocol = await ProtocolService.getById(protocolId);
let agenda = await ProtocolAgendaService.getAll(protocolId);
let decisions = await ProtocolDecisionService.getAll(protocolId);
let presence = await ProtocolPresenceService.getAll(protocolId);
let votings = await ProtocolVotingService.getAll(protocolId);
let iteration = await ProtocolPrintoutService.getCount(protocolId);
let title = `Sitzungsprotokoll - ${new Date(protocol.date).toLocaleDateString("de-DE", {
day: "2-digit",
month: "long",
year: "numeric",
})}`;
let filename = `P_${protocol.title.replace(/[^a-zA-Z0-9]/g, "")}_${iteration + 1}_${new Date().toLocaleDateString()}`;
await PdfExport.renderFile({
template: "protocol.template.html",
title,
filename,
data: {
title: protocol.title,
summary: protocol.summary,
iteration: iteration + 1,
date: new Date(protocol.date).toLocaleDateString("de-DE", {
weekday: "long",
day: "2-digit",
month: "2-digit",
year: "numeric",
}),
start: new Date(protocol.starttime).toLocaleTimeString("de-DE", {
minute: "2-digit",
hour: "2-digit",
}),
end: new Date(protocol.endtime).toLocaleTimeString("de-DE", {
minute: "2-digit",
hour: "2-digit",
}),
agenda,
decisions,
presence: presence.map((p) => p.member),
votings,
},
});
let printout: CreateProtocolPrintoutCommand = {
title,
iteration: iteration + 1,
filename,
protocolId,
};
await ProtocolPrintoutCommandHandler.create(printout);
res.sendStatus(204);
}
/**
* @description synchronize protocol by id
* @param req {Request} Express req object
@ -290,47 +386,3 @@ export async function synchronizeProtocolPrecenseById(req: Request, res: Respons
res.sendStatus(204);
}
/**
* @description render protocol to file by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function printPdf(req: Request, res: Response): Promise<any> {
let protocolId = parseInt(req.params.protocolId);
let protocol = await ProtocolService.getById(protocolId);
let agenda = await ProtocolAgendaService.getAll(protocolId);
let decisions = await ProtocolDecisionService.getAll(protocolId);
let presence = await ProtocolPresenceService.getAll(protocolId);
let votings = await ProtocolVotingService.getAll(protocolId);
await PdfExport.renderFile({
template: "protocol.template.html",
title: protocol.title,
data: {
title: protocol.title,
summary: protocol.summary,
date: new Date(protocol.date).toLocaleDateString("de-DE", {
weekday: "long",
day: "2-digit",
month: "2-digit",
year: "numeric",
}),
start: new Date(protocol.starttime).toLocaleTimeString("de-DE", {
minute: "2-digit",
hour: "2-digit",
}),
end: new Date(protocol.endtime).toLocaleTimeString("de-DE", {
minute: "2-digit",
hour: "2-digit",
}),
agenda,
decisions,
presence: presence.map((p) => p.member),
votings,
},
});
res.sendStatus(204);
}

View file

@ -39,6 +39,8 @@ import { protocolVoting } from "./entity/protocolVoting";
import { ProtocolTables1728563204766 } from "./migrations/1728563204766-protocolTables";
import { ProtocolTableRename1728645611919 } from "./migrations/1728645611919-protocolTableRename";
import { ProtocolTableTypes1728999487170 } from "./migrations/1728999487170-protocolTableTypes";
import { protocolPrintout } from "./entity/protocolPrintout";
import { ProtocolPrintout1729344771434 } from "./migrations/1729344771434-protocolPrintout";
const dataSource = new DataSource({
type: DB_TYPE as any,
@ -73,6 +75,7 @@ const dataSource = new DataSource({
protocolDecision,
protocolPresence,
protocolVoting,
protocolPrintout,
],
migrations: [
Initial1724317398939,
@ -88,6 +91,7 @@ const dataSource = new DataSource({
ProtocolTables1728563204766,
ProtocolTableRename1728645611919,
ProtocolTableTypes1728999487170,
ProtocolPrintout1729344771434,
],
migrationsRun: true,
migrationsTransactionMode: "each",

View file

@ -0,0 +1,30 @@
import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { protocol } from "./protocol";
@Entity()
export class protocolPrintout {
@PrimaryGeneratedColumn("increment")
id: number;
@Column({ type: "varchar", length: 255 })
title: string;
@Column({ type: "int" })
iteration: number;
@Column({ type: "varchar", length: 255 })
filename: string;
@CreateDateColumn()
createdAt: Date;
@Column()
protocolId: number;
@ManyToOne(() => protocol, {
nullable: false,
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
protocol: protocol;
}

View file

@ -0,0 +1,28 @@
import { protocolPrintout } from "../../entity/protocolPrintout";
import { ProtocolPrintoutViewModel } from "../../viewmodel/admin/protocolPrintout.models";
export default abstract class ProtocolPrintoutFactory {
/**
* @description map record to protocolPrintout
* @param {protocol} record
* @returns {ProtocolPrintoutViewModel}
*/
public static mapToSingle(record: protocolPrintout): ProtocolPrintoutViewModel {
return {
id: record.id,
title: record.title,
iteration: record.iteration,
createdAt: record.createdAt,
protocolId: record.protocolId,
};
}
/**
* @description map records to protocolPrintout
* @param {Array<protocol>} records
* @returns {Array<ProtocolPrintoutViewModel>}
*/
public static mapToBase(records: Array<protocolPrintout>): Array<ProtocolPrintoutViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -22,11 +22,21 @@ export abstract class PdfExport {
return readFileSync(process.cwd() + "/src/templates/" + template, "utf8");
}
static async renderFile({ template, title, data }: { template: string; title: string; data: any }) {
static async renderFile({
template,
title,
filename,
data,
}: {
template: string;
title: string;
filename: string;
data: any;
}) {
let document = {
html: this.getTemplate(template),
data,
path: process.cwd() + `/export/${title}.pdf`,
path: process.cwd() + `/export/${filename}.pdf`,
};
await pdf.create(document, options(title));

View file

@ -0,0 +1,44 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
import { DB_TYPE } from "../env.defaults";
export class ProtocolPrintout1729344771434 implements MigrationInterface {
name = "ProtocolPrintout1729344771434";
public async up(queryRunner: QueryRunner): Promise<void> {
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
await queryRunner.createTable(
new Table({
name: "protocol_printout",
columns: [
{ name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" },
{ name: "title", type: "varchar", length: "255", isNullable: false },
{ name: "iteration", type: variableType_int, default: 1, isNullable: false },
{ name: "filename", type: "varchar", length: "255", isNullable: false },
{ name: "createdAt", type: "datetime(6)", isNullable: false, default: "CURRENT_TIMESTAMP(6)" },
{ name: "protocolId", type: variableType_int, isNullable: false },
],
}),
true
);
await queryRunner.createForeignKey(
"protocol_printout",
new TableForeignKey({
columnNames: ["protocolId"],
referencedColumnNames: ["id"],
referencedTableName: "protocol",
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("protocol_printout");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("protocolId") !== -1);
await queryRunner.dropForeignKey("protocol_printout", foreignKey);
await queryRunner.dropTable("protocol_printout");
}
}

View file

@ -3,14 +3,16 @@ import {
createProtocol,
createProtocolAgendaById,
createProtocolDecisonsById,
createProtocolPrintoutById,
createProtocolVotingsById,
getAllProtocols,
getProtocolAgendaById,
getProtocolById,
getProtocolDecisonsById,
getProtocolPrecenseById,
getProtocolPrintoutByIdAndPrint,
getProtocolPrintoutsById,
getProtocolVotingsById,
printPdf,
synchronizeProtocolAgendaById,
synchronizeProtocolById,
synchronizeProtocolDecisonsById,
@ -44,6 +46,14 @@ router.get("/:protocolId/votings", async (req: Request, res: Response) => {
await getProtocolVotingsById(req, res);
});
router.get("/:protocolId/printouts", async (req: Request, res: Response) => {
await getProtocolPrintoutsById(req, res);
});
router.get("/:protocolId/printout/:printoutId", async (req: Request, res: Response) => {
await getProtocolPrintoutByIdAndPrint(req, res);
});
router.post("/", async (req: Request, res: Response) => {
await createProtocol(req, res);
});
@ -60,8 +70,8 @@ router.post("/:protocolId/voting", async (req: Request, res: Response) => {
await createProtocolVotingsById(req, res);
});
router.post("/:protocolId/render", async (req: Request, res: Response) => {
await printPdf(req, res);
router.post("/:protocolId/printout", async (req: Request, res: Response) => {
await createProtocolPrintoutById(req, res);
});
router.patch("/:id/synchronize", async (req: Request, res: Response) => {

View file

@ -0,0 +1,60 @@
import { dataSource } from "../data-source";
import { protocolPrintout } from "../entity/protocolPrintout";
import InternalException from "../exceptions/internalException";
export default abstract class ProtocolPrintoutService {
/**
* @description get all protocolPrintouts
* @returns {Promise<Array<protocolPrintout>>}
*/
static async getAll(protocolId: number): Promise<Array<protocolPrintout>> {
return await dataSource
.getRepository(protocolPrintout)
.createQueryBuilder("protocolPrintout")
.where("protocolPrintout.protocolId = :protocolId", { protocolId })
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("protocolPrintouts not found", err);
});
}
/**
* @description get protocolPrintout by id
* @returns {Promise<protocolPrintout>}
*/
static async getById(id: number, protocolId: number): Promise<protocolPrintout> {
return await dataSource
.getRepository(protocolPrintout)
.createQueryBuilder("protocolPrintout")
.where("protocolPrintout.protocolId = :protocolId", { protocolId })
.andWhere("protocolPrintout.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("protocolPrintout not found by id", err);
});
}
/**
* @description get count of printouts by id
* @returns {Promise<number>}
*/
static async getCount(protocolId: number): Promise<number> {
return await dataSource
.getRepository(protocolPrintout)
.createQueryBuilder("protocolPrintout")
.where("protocolPrintout.protocolId = :protocolId", { protocolId })
.getCount()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("protocolPrintout not found by id", err);
});
}
}

View file

@ -7,7 +7,9 @@
<body>
<h1>{{title}}</h1>
<p>Am {{date}} von {{start}} Uhr bis {{end}} Uhr</p>
<p>{{sumary}}</p>
<p>Ausdruck Nr {{iteration}}</p>
<br />
<p>{{{summary}}}</p>
<h2>Anwesenheit</h2>
<ul>
{{#each presence}}
@ -17,19 +19,22 @@
<h2>Agenda</h2>
<ul>
{{#each agenda}}
<li>{{this}}</li>
<li>{{this.topic}}: {{{this.context}}}</li>
{{/each}}
</ul>
<h2>Entscheidungen</h2>
<ul>
{{#each decisions}}
<li>{{this}}</li>
<li>{{this.topic}}: {{{this.context}}}</li>
{{/each}}
</ul>
<h2>Abstimmungen</h2>
<h2>Abstimmungen - <small>(für|enthalten|gegen)</small></h2>
<ul>
{{#each votings}}
<li>{{this}}</li>
<li>
{{this.topic}}: {{{this.context}}} <br />
({{this.favour}}|{{this.abstain}}|{{this.against}})
</li>
{{/each}}
</ul>
</body>

View file

@ -0,0 +1,7 @@
export interface ProtocolPrintoutViewModel {
id: number;
title: string;
iteration: number;
createdAt: Date;
protocolId: number;
}