From 9da2a98f55e6d7158d983da105ac8db0b7fb68bd Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sat, 19 Oct 2024 16:24:41 +0200 Subject: [PATCH] printout --- src/command/protocolPrintoutCommand.ts | 6 + src/command/protocolPrintoutCommandHandler.ts | 31 ++++ src/controller/admin/protocolController.ts | 140 ++++++++++++------ src/data-source.ts | 4 + src/entity/protocolPrintout.ts | 30 ++++ src/factory/admin/protocolPrintout.ts | 28 ++++ src/helpers/pdfExport.ts | 14 +- .../1729344771434-protocolPrintout.ts | 44 ++++++ src/routes/admin/protocol.ts | 16 +- src/service/protocolPrintoutService.ts | 60 ++++++++ src/templates/protocol.template.html | 15 +- .../admin/protocolPrintout.models.ts | 7 + 12 files changed, 341 insertions(+), 54 deletions(-) create mode 100644 src/command/protocolPrintoutCommand.ts create mode 100644 src/command/protocolPrintoutCommandHandler.ts create mode 100644 src/entity/protocolPrintout.ts create mode 100644 src/factory/admin/protocolPrintout.ts create mode 100644 src/migrations/1729344771434-protocolPrintout.ts create mode 100644 src/service/protocolPrintoutService.ts create mode 100644 src/viewmodel/admin/protocolPrintout.models.ts diff --git a/src/command/protocolPrintoutCommand.ts b/src/command/protocolPrintoutCommand.ts new file mode 100644 index 0000000..6491f9d --- /dev/null +++ b/src/command/protocolPrintoutCommand.ts @@ -0,0 +1,6 @@ +export interface CreateProtocolPrintoutCommand { + title: string; + iteration: number; + filename: string; + protocolId: number; +} diff --git a/src/command/protocolPrintoutCommandHandler.ts b/src/command/protocolPrintoutCommandHandler.ts new file mode 100644 index 0000000..c1cdcbe --- /dev/null +++ b/src/command/protocolPrintoutCommandHandler.ts @@ -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} + */ + static async create(printout: CreateProtocolPrintoutCommand): Promise { + 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); + }); + } +} diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index f936274..d6f0310 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -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 { + 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 { + 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 { + 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 { - 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); -} diff --git a/src/data-source.ts b/src/data-source.ts index 5a1c1f3..10b2991 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -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", diff --git a/src/entity/protocolPrintout.ts b/src/entity/protocolPrintout.ts new file mode 100644 index 0000000..311b407 --- /dev/null +++ b/src/entity/protocolPrintout.ts @@ -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; +} diff --git a/src/factory/admin/protocolPrintout.ts b/src/factory/admin/protocolPrintout.ts new file mode 100644 index 0000000..a93fad1 --- /dev/null +++ b/src/factory/admin/protocolPrintout.ts @@ -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} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/helpers/pdfExport.ts b/src/helpers/pdfExport.ts index 4048f5e..353d8dd 100644 --- a/src/helpers/pdfExport.ts +++ b/src/helpers/pdfExport.ts @@ -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)); diff --git a/src/migrations/1729344771434-protocolPrintout.ts b/src/migrations/1729344771434-protocolPrintout.ts new file mode 100644 index 0000000..25c50fe --- /dev/null +++ b/src/migrations/1729344771434-protocolPrintout.ts @@ -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 { + 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 { + 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"); + } +} diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index 4b07da7..7cc29a4 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -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) => { diff --git a/src/service/protocolPrintoutService.ts b/src/service/protocolPrintoutService.ts new file mode 100644 index 0000000..aaf39ed --- /dev/null +++ b/src/service/protocolPrintoutService.ts @@ -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>} + */ + static async getAll(protocolId: number): Promise> { + 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} + */ + static async getById(id: number, protocolId: number): Promise { + 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} + */ + static async getCount(protocolId: number): Promise { + 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); + }); + } +} diff --git a/src/templates/protocol.template.html b/src/templates/protocol.template.html index e5e22b3..4a49c61 100644 --- a/src/templates/protocol.template.html +++ b/src/templates/protocol.template.html @@ -7,7 +7,9 @@

{{title}}

Am {{date}} von {{start}} Uhr bis {{end}} Uhr

-

{{sumary}}

+

Ausdruck Nr {{iteration}}

+
+

{{{summary}}}

Anwesenheit

    {{#each presence}} @@ -17,19 +19,22 @@

    Agenda

      {{#each agenda}} -
    • {{this}}
    • +
    • {{this.topic}}: {{{this.context}}}
    • {{/each}}

    Entscheidungen

      {{#each decisions}} -
    • {{this}}
    • +
    • {{this.topic}}: {{{this.context}}}
    • {{/each}}
    -

    Abstimmungen

    +

    Abstimmungen - (für|enthalten|gegen)

      {{#each votings}} -
    • {{this}}
    • +
    • + {{this.topic}}: {{{this.context}}}
      + ({{this.favour}}|{{this.abstain}}|{{this.against}}) +
    • {{/each}}
    diff --git a/src/viewmodel/admin/protocolPrintout.models.ts b/src/viewmodel/admin/protocolPrintout.models.ts new file mode 100644 index 0000000..ff60cdb --- /dev/null +++ b/src/viewmodel/admin/protocolPrintout.models.ts @@ -0,0 +1,7 @@ +export interface ProtocolPrintoutViewModel { + id: number; + title: string; + iteration: number; + createdAt: Date; + protocolId: number; +}