From d6a13d657ba265e42436fb6a7aba0b9b724df991 Mon Sep 17 00:00:00 2001 From: Julian Krauser <jkrauser209@gmail.com> Date: Sat, 15 Mar 2025 09:05:22 +0100 Subject: [PATCH 1/4] member base template --- src/templates/member.body.template.html | 49 +++++++++++++++++++++++ src/templates/member.footer.template.html | 3 ++ 2 files changed, 52 insertions(+) create mode 100644 src/templates/member.body.template.html create mode 100644 src/templates/member.footer.template.html diff --git a/src/templates/member.body.template.html b/src/templates/member.body.template.html new file mode 100644 index 0000000..9129600 --- /dev/null +++ b/src/templates/member.body.template.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>Mitglied</title> + </head> + <body> + <h1>{{member.lastname}} {{member.firstname}}{{#if member.nameaffix}} - {{member.nameaffix}}{{/if}}</h1> + <p>Mitglieds-Ausdruck Stand {{today}}</p> + <br /> + + <h2>Agenda</h2> + {{#each ---}} + <div> + <h3></h3> + <span></span> + </div> + <br /> + {{/each}} + </body> + <style> + h2, + h3, + p, + span, + ul, + li { + padding: 0; + margin: 0; + } + + h1, + h2 { + color: #990b00; + } + + h2 { + margin-bottom: 5px; + } + + table, + th, + td { + border: 1px solid black; + border-collapse: collapse; + text-align: start; + } + </style> +</html> diff --git a/src/templates/member.footer.template.html b/src/templates/member.footer.template.html new file mode 100644 index 0000000..7a3190b --- /dev/null +++ b/src/templates/member.footer.template.html @@ -0,0 +1,3 @@ +<div style="font-size: 10px; text-align: center; width: 100%; color: #888"> + Seite <span class="pageNumber"></span> von <span class="totalPages"></span> +</div> -- 2.45.3 From 2f72cc392562c54fec0c0d0c7f4e214dcfdc3298 Mon Sep 17 00:00:00 2001 From: Julian Krauser <jkrauser209@gmail.com> Date: Sun, 16 Mar 2025 17:42:51 +0100 Subject: [PATCH 2/4] template + route & controller --- src/controller/admin/club/memberController.ts | 38 ++++++++++++ src/routes/admin/club/member.ts | 5 ++ src/templates/member.body.template.html | 62 +++++++++++++++++-- 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/controller/admin/club/memberController.ts b/src/controller/admin/club/memberController.ts index 91c51f8..86bea81 100644 --- a/src/controller/admin/club/memberController.ts +++ b/src/controller/admin/club/memberController.ts @@ -118,6 +118,44 @@ export async function getMemberStatisticsById(req: Request, res: Response): Prom res.json(MemberFactory.mapToMemberStatistic(member)); } +/** + * @description get member printout by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getMemberPrintoutById(req: Request, res: Response): Promise<any> { + const memberId = req.params.id; + let member = await MemberService.getById(memberId); + let memberships = await MembershipService.getAll(memberId); + let awards = await MemberAwardService.getAll(memberId); + let qualifications = await MemberQualificationService.getAll(memberId); + let positions = await MemberExecutivePositionService.getAll(memberId); + let communications = await CommunicationService.getAll(memberId); + + let pdf = await PdfExport.renderFile({ + title: "Mitglieder-Ausdruck", + template: "member", + saveToDisk: false, + data: { + member, + memberships, + awards, + qualifications, + positions, + communications, + }, + }); + + let pdfbuffer = Buffer.from(pdf); + + res.setHeader("Content-Type", "application/pdf"); + res.setHeader("Content-Length", pdfbuffer.byteLength); + res.setHeader("Content-Disposition", "inline; filename=preview.pdf"); + + res.send(pdfbuffer); +} + /** * @description get memberships by member * @param req {Request} Express req object diff --git a/src/routes/admin/club/member.ts b/src/routes/admin/club/member.ts index 5f9d032..541487e 100644 --- a/src/routes/admin/club/member.ts +++ b/src/routes/admin/club/member.ts @@ -21,6 +21,7 @@ import { getExecutivePositionByMemberAndRecord, getExecutivePositionsByMember, getMemberById, + getMemberPrintoutById, getMembersByIds, getMembershipByMemberAndRecord, getMembershipsByMember, @@ -55,6 +56,10 @@ router.get("/:id/statistics", async (req: Request, res: Response) => { await getMemberStatisticsById(req, res); }); +router.get("/:id/print", async (req: Request, res: Response) => { + await getMemberPrintoutById(req, res); +}); + router.get("/print/namelist", async (req: Request, res: Response) => { await createMemberPrintoutList(req, res); }); diff --git a/src/templates/member.body.template.html b/src/templates/member.body.template.html index 9129600..a777c2a 100644 --- a/src/templates/member.body.template.html +++ b/src/templates/member.body.template.html @@ -5,18 +5,68 @@ <title>Mitglied</title> </head> <body> - <h1>{{member.lastname}} {{member.firstname}}{{#if member.nameaffix}} - {{member.nameaffix}}{{/if}}</h1> + <h1> + {{member.salutation}} {{member.lastname}} {{member.firstname}}{{#if member.nameaffix}} - + {{member.nameaffix}}{{/if}} + </h1> <p>Mitglieds-Ausdruck Stand {{today}}</p> <br /> - <h2>Agenda</h2> - {{#each ---}} + <p>geboren: {{member.birthdate}}</p> + <p>eingetreten: {{member.firstMembershipEntry.start}}</p> + {{#if member.lastMembershipEntry}} + <p>ausgetreten: {{member.lastMembershipEntry.end}}</p> + {{/if}} {{#if memberships.length}} + <br /> + <h2>Mitgliedschaften</h2> + {{#each memberships}} <div> - <h3></h3> - <span></span> + <h3>{{this.status.status}}: {{this.start}} - {{this.end ?? 'heute'}}</h3> + {{#if this.terminationReason}} + <p>beendet, weil:{{this.terminationReason}}</p> + {{/if}} </div> <br /> - {{/each}} + {{/each}} {{/if}} {{#if memberships.length}} + <br /> + <h2>Vereinsämter</h2> + {{#each positions}} + <div> + <h3>{{this.executivePosition.position}}: {{this.start}} - {{this.end ?? 'heute'}}</h3> + {{#if this.note}} + <p>Notiz: {{this.note}}</p> + {{/if}} + </div> + <br /> + {{/each}} {{/if}} {{#if awards.length}} + <br /> + <h2>Auszeichnungen</h2> + {{#each awards}} + <div> + <h3>{{this.award.award}}: {{this.date}}</h3> + {{#if this.given}} + <p>wurde vergeben</p> + {{else}} + <p>wurde verwehrt / zurückgewiesen</p> + {{/if}} {{#if this.note}} + <p>Notiz: {{this.note}}</p> + {{/if}} + </div> + <br /> + {{/each}} {{/if}} {{#if qualifications.length}} + <br /> + <h2>Qualifikationen</h2> + {{#each qualifications}} + <div> + <h3>{{this.qualification.qualification}}: {{this.date}}</h3> + {{#if this.terminationReason}} + <p>beendet, weil:{{this.terminationReason}}</p> + {{/if}} {{/if}} {{#if this.note}} + <p>Notiz: {{this.note}}</p> + {{/if}} + </div> + <br /> + {{/each}} {{/if}} </body> <style> h2, -- 2.45.3 From de5e4afffb3538b25034ac2138b57de3e706cded Mon Sep 17 00:00:00 2001 From: Julian Krauser <jkrauser209@gmail.com> Date: Mon, 17 Mar 2025 11:56:17 +0100 Subject: [PATCH 3/4] demo data & template usage --- src/controller/admin/club/memberController.ts | 6 + src/data-source.ts | 3 +- src/demodata/member.data.ts | 138 ++++++++++++++++++ src/helpers/demoDataHelper.ts | 7 +- .../1742207245862-memberPrintoutTemplates.ts | 20 +++ src/templates/member.body.template.html | 10 +- 6 files changed, 176 insertions(+), 8 deletions(-) create mode 100644 src/demodata/member.data.ts create mode 100644 src/migrations/1742207245862-memberPrintoutTemplates.ts diff --git a/src/controller/admin/club/memberController.ts b/src/controller/admin/club/memberController.ts index 86bea81..b44f690 100644 --- a/src/controller/admin/club/memberController.ts +++ b/src/controller/admin/club/memberController.ts @@ -138,6 +138,12 @@ export async function getMemberPrintoutById(req: Request, res: Response): Promis template: "member", saveToDisk: false, data: { + today: new Date().toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric", + }), member, memberships, awards, diff --git a/src/data-source.ts b/src/data-source.ts index 6babcb1..87af7a0 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -47,6 +47,7 @@ import { salutation } from "./entity/configuration/salutation"; import { BackupAndResetDatabase1738166124200 } from "./migrations/1738166124200-BackupAndResetDatabase"; import { CreateSchema1738166167472 } from "./migrations/1738166167472-CreateSchema"; +import { MemberPrintoutTemplates1742207245862 } from "./migrations/1742207245862-memberPrintoutTemplates"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -100,7 +101,7 @@ const dataSource = new DataSource({ webapi, webapiPermission, ], - migrations: [BackupAndResetDatabase1738166124200, CreateSchema1738166167472], + migrations: [BackupAndResetDatabase1738166124200, CreateSchema1738166167472, MemberPrintoutTemplates1742207245862], migrationsRun: true, migrationsTransactionMode: "each", subscribers: [], diff --git a/src/demodata/member.data.ts b/src/demodata/member.data.ts new file mode 100644 index 0000000..150ae00 --- /dev/null +++ b/src/demodata/member.data.ts @@ -0,0 +1,138 @@ +import { communication } from "../entity/club/member/communication"; +import { member } from "../entity/club/member/member"; +import { memberAwards } from "../entity/club/member/memberAwards"; +import { memberExecutivePositions } from "../entity/club/member/memberExecutivePositions"; +import { memberQualifications } from "../entity/club/member/memberQualifications"; +import { membership } from "../entity/club/member/membership"; + +export const memberDemoData: { + member: Partial<member>; + today: string; + memberships: Array<Partial<membership>>; + awards: Array<Partial<memberAwards>>; + qualifications: Array<Partial<memberQualifications>>; + positions: Array<Partial<memberExecutivePositions>>; + communications: Array<Partial<communication>>; +} = { + today: "Montag, 17.03.2025", + member: { + id: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + firstname: "Julian", + lastname: "Krauser", + nameaffix: "", + //@ts-ignore + birthdate: "2003-09-20", + internalId: "1312", + salutationId: 47, + salutation: { id: 47, salutation: "Herr", members: [] }, + firstMembershipEntry: { + id: 8681, + //@ts-ignore + start: "2017-11-13", + end: null, + terminationReason: null, + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + statusId: 34, + status: { id: 34, status: "aktiv", memberships: [] }, + //@ts-ignore + member: {}, + }, + lastMembershipEntry: { + id: 8681, + //@ts-ignore + start: "2017-11-13", + end: null, + terminationReason: null, + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + statusId: 34, + status: { id: 34, status: "aktiv", memberships: [] }, + //@ts-ignore + member: {}, + }, + preferredCommunication: [ + { + id: 7031, + preferred: true, + isSMSAlarming: false, + isSendNewsletter: true, + mobile: "", + email: "julian.krauser@jk-effects.com", + postalCode: "", + city: "", + street: "", + streetNumber: 0, + streetNumberAddition: "", + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + typeId: 46, + type: { id: 46, type: "Email", useColumns: ["email"], communications: [] }, + // @ts-ignore + member: {}, + }, + ], + smsAlarming: [], + sendNewsletter: { + id: 7031, + preferred: true, + isSMSAlarming: false, + isSendNewsletter: true, + mobile: "", + email: "julian.krauser@jk-effects.com ", + postalCode: "", + city: "", + street: "", + streetNumber: 0, + streetNumberAddition: "", + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + typeId: 46, + type: { id: 46, type: "Email", useColumns: ["email"], communications: [] }, + // @ts-ignore + member: {}, + }, + }, + memberships: [ + { + id: 8681, + //@ts-ignore + start: "2017-11-13", + end: null, + terminationReason: null, + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + statusId: 34, + status: { id: 34, status: "aktiv", memberships: [] }, + }, + ], + awards: [], + qualifications: [], + positions: [ + { + id: 696, + note: "", + //@ts-ignore + start: "2025-01-06", + end: null, + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + executivePositionId: 192, + executivePosition: { id: 192, position: "Schriftführer", members: [] }, + }, + ], + communications: [ + { + id: 7031, + preferred: true, + isSMSAlarming: false, + isSendNewsletter: true, + mobile: "", + email: "julian.krauser@jk-effects.com", + postalCode: "", + city: "", + street: "", + streetNumber: 0, + streetNumberAddition: "", + memberId: "2fe205f8-8ae8-4218-839f-af3456d3f39d", + typeId: 46, + //@ts-ignore + member: {}, + type: { id: 46, type: "Email", useColumns: ["email"], communications: [] }, + }, + ], +}; diff --git a/src/helpers/demoDataHelper.ts b/src/helpers/demoDataHelper.ts index 3187ca1..45020a4 100644 --- a/src/helpers/demoDataHelper.ts +++ b/src/helpers/demoDataHelper.ts @@ -1,10 +1,11 @@ import { newsletterDemoData } from "../demodata/newsletter.data"; import { protocolDemoData } from "../demodata/protocol.data"; import { PermissionModule } from "../type/permissionTypes"; -import {memberlistDemoData} from "../demodata/member.list.data"; +import { memberlistDemoData } from "../demodata/member.list.data"; +import { memberDemoData } from "../demodata/member.data"; export abstract class DemoDataHelper { - static getData(scope: `${PermissionModule}`|`${PermissionModule}.${string}`) { + static getData(scope: `${PermissionModule}` | `${PermissionModule}.${string}`) { switch (scope) { case "protocol": return protocolDemoData; @@ -12,6 +13,8 @@ export abstract class DemoDataHelper { return newsletterDemoData; case "member.list": return memberlistDemoData; + case "member": + return memberDemoData; default: return {}; } diff --git a/src/migrations/1742207245862-memberPrintoutTemplates.ts b/src/migrations/1742207245862-memberPrintoutTemplates.ts new file mode 100644 index 0000000..a799d78 --- /dev/null +++ b/src/migrations/1742207245862-memberPrintoutTemplates.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; +import { templateUsage } from "../entity/configuration/templateUsage"; + +export class MemberPrintoutTemplates1742207245862 implements MigrationInterface { + name = "MemberPrintoutTemplates1742207245862"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.manager + .createQueryBuilder() + .insert() + .into(templateUsage) + .values([{ scope: "member" }]) + .orUpdate(["headerId", "bodyId", "footerId", "headerHeight", "footerHeight"], ["scope"]) + .execute(); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.manager.createQueryBuilder().delete().from(templateUsage).where({ scope: "member" }).execute(); + } +} diff --git a/src/templates/member.body.template.html b/src/templates/member.body.template.html index a777c2a..5d499ae 100644 --- a/src/templates/member.body.template.html +++ b/src/templates/member.body.template.html @@ -6,7 +6,7 @@ </head> <body> <h1> - {{member.salutation}} {{member.lastname}} {{member.firstname}}{{#if member.nameaffix}} - + {{member.salutation.salutation}} {{member.lastname}} {{member.firstname}}{{#if member.nameaffix}} - {{member.nameaffix}}{{/if}} </h1> <p>Mitglieds-Ausdruck Stand {{today}}</p> @@ -14,14 +14,14 @@ <p>geboren: {{member.birthdate}}</p> <p>eingetreten: {{member.firstMembershipEntry.start}}</p> - {{#if member.lastMembershipEntry}} + {{#if member.lastMembershipEntry?.end}} <p>ausgetreten: {{member.lastMembershipEntry.end}}</p> {{/if}} {{#if memberships.length}} <br /> <h2>Mitgliedschaften</h2> {{#each memberships}} <div> - <h3>{{this.status.status}}: {{this.start}} - {{this.end ?? 'heute'}}</h3> + <h3>{{this.status.status}}: {{this.start}} - {{#if this.end}}{{this.end}}{{else}}heute{{/if}}</h3> {{#if this.terminationReason}} <p>beendet, weil:{{this.terminationReason}}</p> {{/if}} @@ -32,7 +32,7 @@ <h2>Vereinsämter</h2> {{#each positions}} <div> - <h3>{{this.executivePosition.position}}: {{this.start}} - {{this.end ?? 'heute'}}</h3> + <h3>{{this.executivePosition.position}}: {{this.start}} - {{#if this.end}}{{this.end}}{{else}}heute{{/if}}</h3> {{#if this.note}} <p>Notiz: {{this.note}}</p> {{/if}} @@ -61,7 +61,7 @@ <h3>{{this.qualification.qualification}}: {{this.date}}</h3> {{#if this.terminationReason}} <p>beendet, weil:{{this.terminationReason}}</p> - {{/if}} {{/if}} {{#if this.note}} + {{/if}} {{#if this.note}} <p>Notiz: {{this.note}}</p> {{/if}} </div> -- 2.45.3 From 14957c2abc24bb1caec43c63852dda56c2b71d0c Mon Sep 17 00:00:00 2001 From: Julian Krauser <jkrauser209@gmail.com> Date: Tue, 18 Mar 2025 10:26:05 +0100 Subject: [PATCH 4/4] template improvements & handlebars helpers --- src/controller/admin/club/memberController.ts | 7 +---- src/helpers/templateHelper.ts | 27 +++++++++++++++++++ src/templates/member.body.template.html | 23 +++++++++------- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/controller/admin/club/memberController.ts b/src/controller/admin/club/memberController.ts index b44f690..a31e514 100644 --- a/src/controller/admin/club/memberController.ts +++ b/src/controller/admin/club/memberController.ts @@ -138,12 +138,7 @@ export async function getMemberPrintoutById(req: Request, res: Response): Promis template: "member", saveToDisk: false, data: { - today: new Date().toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "2-digit", - year: "numeric", - }), + today: new Date(), member, memberships, awards, diff --git a/src/helpers/templateHelper.ts b/src/helpers/templateHelper.ts index a8a14b1..af1c832 100644 --- a/src/helpers/templateHelper.ts +++ b/src/helpers/templateHelper.ts @@ -4,6 +4,33 @@ import TemplateUsageService from "../service/configuration/templateUsageService" import Handlebars, { template } from "handlebars"; import { FileSystemHelper } from "./fileSystemHelper"; +Handlebars.registerHelper("date", function (aString) { + return new Date(aString).toLocaleDateString("de-DE", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); +}); + +Handlebars.registerHelper("longdate", function (aString) { + return new Date(aString).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric", + }); +}); + +Handlebars.registerHelper("datetime", function (aString) { + return new Date(aString).toLocaleDateString("de-DE", { + day: "2-digit", + month: "2-digit", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +}); + export abstract class TemplateHelper { static getTemplateFromFile(template: string) { return FileSystemHelper.readTemplateFile(`/src/templates/${template}.template.html`); diff --git a/src/templates/member.body.template.html b/src/templates/member.body.template.html index 5d499ae..67f8821 100644 --- a/src/templates/member.body.template.html +++ b/src/templates/member.body.template.html @@ -6,33 +6,36 @@ </head> <body> <h1> - {{member.salutation.salutation}} {{member.lastname}} {{member.firstname}}{{#if member.nameaffix}} - + {{member.salutation.salutation}} {{member.lastname}}, {{member.firstname}}{{#if member.nameaffix}} - {{member.nameaffix}}{{/if}} </h1> - <p>Mitglieds-Ausdruck Stand {{today}}</p> + <p>Mitglieds-Ausdruck Stand {{longdate today}}</p> <br /> - <p>geboren: {{member.birthdate}}</p> - <p>eingetreten: {{member.firstMembershipEntry.start}}</p> + <p>geboren: {{date member.birthdate}}</p> + <p>eingetreten: {{date member.firstMembershipEntry.start}}</p> {{#if member.lastMembershipEntry?.end}} - <p>ausgetreten: {{member.lastMembershipEntry.end}}</p> + <p>ausgetreten: {{date member.lastMembershipEntry.end}}</p> {{/if}} {{#if memberships.length}} <br /> <h2>Mitgliedschaften</h2> {{#each memberships}} <div> - <h3>{{this.status.status}}: {{this.start}} - {{#if this.end}}{{this.end}}{{else}}heute{{/if}}</h3> + <h3>{{this.status.status}}: {{date this.start}} bis {{#if this.end}}{{date this.end}}{{else}}heute{{/if}}</h3> {{#if this.terminationReason}} <p>beendet, weil:{{this.terminationReason}}</p> {{/if}} </div> <br /> - {{/each}} {{/if}} {{#if memberships.length}} + {{/each}} {{/if}} {{#if positions.length}} <br /> <h2>Vereinsämter</h2> {{#each positions}} <div> - <h3>{{this.executivePosition.position}}: {{this.start}} - {{#if this.end}}{{this.end}}{{else}}heute{{/if}}</h3> + <h3> + {{this.executivePosition.position}}: {{date this.start}} bis {{#if this.end}}{{date + this.end}}{{else}}heute{{/if}} + </h3> {{#if this.note}} <p>Notiz: {{this.note}}</p> {{/if}} @@ -43,7 +46,7 @@ <h2>Auszeichnungen</h2> {{#each awards}} <div> - <h3>{{this.award.award}}: {{this.date}}</h3> + <h3>{{this.award.award}}: {{date this.date}}</h3> {{#if this.given}} <p>wurde vergeben</p> {{else}} @@ -58,7 +61,7 @@ <h2>Qualifikationen</h2> {{#each qualifications}} <div> - <h3>{{this.qualification.qualification}}: {{this.date}}</h3> + <h3>{{this.qualification.qualification}}: {{date this.date}}</h3> {{#if this.terminationReason}} <p>beendet, weil:{{this.terminationReason}}</p> {{/if}} {{#if this.note}} -- 2.45.3