protocol absence

This commit is contained in:
Julian Krauser 2025-01-05 12:12:53 +01:00
parent 455589e2b5
commit 161a9dc51c
10 changed files with 86 additions and 23 deletions

View file

@ -1,4 +1,9 @@
export interface SynchronizeProtocolPresenceCommand { export interface SynchronizeProtocolPresenceCommand {
memberIds: Array<number>; members: Array<ProtocolPresenceCommand>;
protocolId: number; protocolId: number;
} }
export interface ProtocolPresenceCommand {
memberId: number;
absent: boolean;
}

View file

@ -1,9 +1,9 @@
import { DeleteResult, EntityManager, InsertResult } from "typeorm"; import { DeleteResult, EntityManager, InsertResult, UpdateResult } from "typeorm";
import { dataSource } from "../data-source"; import { dataSource } from "../data-source";
import { protocolPresence } from "../entity/protocolPresence"; import { protocolPresence } from "../entity/protocolPresence";
import InternalException from "../exceptions/internalException"; import InternalException from "../exceptions/internalException";
import ProtocolPresenceService from "../service/protocolPrecenseService"; import ProtocolPresenceService from "../service/protocolPrecenseService";
import { SynchronizeProtocolPresenceCommand } from "./protocolPresenceCommand"; import { ProtocolPresenceCommand, SynchronizeProtocolPresenceCommand } from "./protocolPresenceCommand";
export default abstract class ProtocolPresenceCommandHandler { export default abstract class ProtocolPresenceCommandHandler {
/** /**
@ -12,14 +12,21 @@ export default abstract class ProtocolPresenceCommandHandler {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async sync(syncProtocolPresences: SynchronizeProtocolPresenceCommand): Promise<void> { static async sync(syncProtocolPresences: SynchronizeProtocolPresenceCommand): Promise<void> {
let currentPresence = (await ProtocolPresenceService.getAll(syncProtocolPresences.protocolId)).map( let currentPresence = await ProtocolPresenceService.getAll(syncProtocolPresences.protocolId);
(r) => r.memberId
);
return await dataSource.manager return await dataSource.manager
.transaction(async (manager) => { .transaction(async (manager) => {
let newMembers = syncProtocolPresences.memberIds.filter((r) => !currentPresence.includes(r)); let newMembers = syncProtocolPresences.members.filter(
let removeMembers = currentPresence.filter((r) => !syncProtocolPresences.memberIds.includes(r)); (r) => !currentPresence.some((cp) => cp.memberId == r.memberId)
);
let removeMembers = currentPresence.filter(
(r) => !syncProtocolPresences.members.some((cp) => cp.memberId == r.memberId)
);
let keptMembers = syncProtocolPresences.members.filter(
(m) =>
currentPresence.some((cd) => cd.memberId == m.memberId) &&
!removeMembers.some((cd) => cd.memberId == m.memberId)
);
if (newMembers.length != 0) { if (newMembers.length != 0) {
await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers); await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers);
@ -28,6 +35,10 @@ export default abstract class ProtocolPresenceCommandHandler {
if (removeMembers.length != 0) { if (removeMembers.length != 0) {
await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers); await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers);
} }
for (const member of keptMembers) {
await this.syncPresenceUpdate(manager, syncProtocolPresences.protocolId, member);
}
}) })
.then(() => {}) .then(() => {})
.catch((err) => { .catch((err) => {
@ -38,7 +49,7 @@ export default abstract class ProtocolPresenceCommandHandler {
private static async syncPresenceAdd( private static async syncPresenceAdd(
manager: EntityManager, manager: EntityManager,
protocolId: number, protocolId: number,
memberIds: Array<number> memberIds: Array<ProtocolPresenceCommand>
): Promise<InsertResult> { ): Promise<InsertResult> {
return await manager return await manager
.createQueryBuilder() .createQueryBuilder()
@ -46,23 +57,39 @@ export default abstract class ProtocolPresenceCommandHandler {
.into(protocolPresence) .into(protocolPresence)
.values( .values(
memberIds.map((m) => ({ memberIds.map((m) => ({
...m,
protocolId, protocolId,
memberId: m,
})) }))
) )
.execute(); .execute();
} }
private static async syncPresenceUpdate(
manager: EntityManager,
protocolId: number,
member: ProtocolPresenceCommand
): Promise<UpdateResult> {
return await manager
.createQueryBuilder()
.update(protocolPresence)
.set({
absent: member.absent,
})
.where("memberId = :memberId", { memberId: member.memberId })
.andWhere("protocolId = :protocolId", { protocolId })
.execute();
}
private static async syncPresenceRemove( private static async syncPresenceRemove(
manager: EntityManager, manager: EntityManager,
protocolId: number, protocolId: number,
memberIds: Array<number> members: Array<ProtocolPresenceCommand>
): Promise<DeleteResult> { ): Promise<DeleteResult> {
return await manager return await manager
.createQueryBuilder() .createQueryBuilder()
.delete() .delete()
.from(protocolPresence) .from(protocolPresence)
.where("memberId IN (:...ids)", { ids: memberIds }) .where("memberId IN (:...ids)", { ids: members.map((m) => m.memberId) })
.andWhere("protocolId = :protocolId", { protocolId }) .andWhere("protocolId = :protocolId", { protocolId })
.execute(); .execute();
} }

View file

@ -28,6 +28,7 @@ import ProtocolPrintoutFactory from "../../factory/admin/protocolPrintout";
import { CreateProtocolPrintoutCommand } from "../../command/protocolPrintoutCommand"; import { CreateProtocolPrintoutCommand } from "../../command/protocolPrintoutCommand";
import ProtocolPrintoutCommandHandler from "../../command/protocolPrintoutCommandHandler"; import ProtocolPrintoutCommandHandler from "../../command/protocolPrintoutCommandHandler";
import { FileSystemHelper } from "../../helpers/fileSystemHelper"; import { FileSystemHelper } from "../../helpers/fileSystemHelper";
import { ProtocolPresenceViewModel } from "../../viewmodel/admin/protocolPresence.models";
/** /**
* @description get all protocols * @description get all protocols
@ -228,7 +229,7 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
let votings = await ProtocolVotingService.getAll(protocolId); let votings = await ProtocolVotingService.getAll(protocolId);
let iteration = await ProtocolPrintoutService.getCount(protocolId); let iteration = await ProtocolPrintoutService.getCount(protocolId);
let title = `Sitzungsprotokoll - ${new Date(protocol.date).toLocaleDateString("de-DE", { let title = `${protocol.title} - ${new Date(protocol.date).toLocaleDateString("de-DE", {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
@ -258,7 +259,8 @@ export async function createProtocolPrintoutById(req: Request, res: Response): P
end: protocol.endtime, end: protocol.endtime,
agenda, agenda,
decisions, decisions,
presence: presence.map((p) => p.member), presence: presence.filter((p) => !p.absent).map((p) => p.member),
absent: presence.filter((p) => p.absent).map((p) => p.member),
votings, votings,
}, },
}); });
@ -381,10 +383,13 @@ export async function synchronizeProtocolVotingsById(req: Request, res: Response
*/ */
export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise<any> { export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise<any> {
let protocolId = parseInt(req.params.protocolId); let protocolId = parseInt(req.params.protocolId);
let presence = req.body.presence as Array<number>; let presence = req.body.presence as Array<ProtocolPresenceViewModel>;
let syncPresence: SynchronizeProtocolPresenceCommand = { let syncPresence: SynchronizeProtocolPresenceCommand = {
memberIds: presence, members: presence.map((p) => ({
memberId: p.memberId,
absent: p.absent,
})),
protocolId, protocolId,
}; };
await ProtocolPresenceCommandHandler.sync(syncPresence); await ProtocolPresenceCommandHandler.sync(syncPresence);

View file

@ -64,6 +64,7 @@ import { NewsletterConfig1735207446910 } from "./migrations/1735207446910-newsle
import { TemplateMargins1735733514043 } from "./migrations/1735733514043-templateMargins"; import { TemplateMargins1735733514043 } from "./migrations/1735733514043-templateMargins";
import { InternalId1735822722235 } from "./migrations/1735822722235-internalId"; import { InternalId1735822722235 } from "./migrations/1735822722235-internalId";
import { PostalCode1735927918979 } from "./migrations/1735927918979-postalCode"; import { PostalCode1735927918979 } from "./migrations/1735927918979-postalCode";
import { ProtocolAbsent1736072179716 } from "./migrations/1736072179716-protocolAbsent";
const dataSource = new DataSource({ const dataSource = new DataSource({
type: DB_TYPE as any, type: DB_TYPE as any,
@ -138,6 +139,7 @@ const dataSource = new DataSource({
TemplateMargins1735733514043, TemplateMargins1735733514043,
InternalId1735822722235, InternalId1735822722235,
PostalCode1735927918979, PostalCode1735927918979,
ProtocolAbsent1736072179716,
], ],
migrationsRun: true, migrationsRun: true,
migrationsTransactionMode: "each", migrationsTransactionMode: "each",

View file

@ -13,6 +13,7 @@ export const protocolDemoData: {
agenda: Array<Partial<protocolAgenda>>; agenda: Array<Partial<protocolAgenda>>;
decisions: Array<Partial<protocolDecision>>; decisions: Array<Partial<protocolDecision>>;
presence: Array<Partial<member>>; presence: Array<Partial<member>>;
absent: Array<Partial<member>>;
votings: Array<Partial<protocolVoting>>; votings: Array<Partial<protocolVoting>>;
} = { } = {
title: "Beispiel Protokoll Daten", title: "Beispiel Protokoll Daten",
@ -44,6 +45,7 @@ export const protocolDemoData: {
lastname: "Krauser", lastname: "Krauser",
}, },
], ],
absent: [],
votings: [ votings: [
{ {
topic: "Abstimmung xy", topic: "Abstimmung xy",

View file

@ -10,6 +10,9 @@ export class protocolPresence {
@PrimaryColumn() @PrimaryColumn()
protocolId: number; protocolId: number;
@Column({ type: "boolean", default: false })
absent: boolean;
@ManyToOne(() => member, { @ManyToOne(() => member, {
nullable: false, nullable: false,
onDelete: "CASCADE", onDelete: "CASCADE",

View file

@ -11,7 +11,7 @@ export default abstract class ProtocolPresenceFactory {
public static mapToSingle(record: protocolPresence): ProtocolPresenceViewModel { public static mapToSingle(record: protocolPresence): ProtocolPresenceViewModel {
return { return {
memberId: record.member.id, memberId: record.member.id,
member: MemberFactory.mapToSingle(record.member), absent: record.absent,
protocolId: record.protocolId, protocolId: record.protocolId,
}; };
} }

View file

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
export class ProtocolAbsent1736072179716 implements MigrationInterface {
name = "ProtocolAbsent1736072179716";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"protocol_presence",
new TableColumn({
name: "absent",
type: "tinyint",
default: "0",
isNullable: false,
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn("protocol_presence", "absent");
}
}

View file

@ -14,11 +14,9 @@
<br /> <br />
<br /> <br />
<h2>Anwesenheit ({{presence.length}})</h2> <h2>Anwesenheit ({{presence.length}})</h2>
<ul> <p>{{#each presence}} {{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}} {{/each}}</p>
{{#each presence}} <h2>Abwesenheit ({{absent.length}})</h2>
<li>{{this.firstname}} {{this.lastname}}</li> <p>{{#each absent}} {{this.firstname}} {{this.lastname}}{{#unless @last}}, {{/unless}} {{/each}}</p>
{{/each}}
</ul>
<br /> <br />
<h2>Agenda</h2> <h2>Agenda</h2>
{{#each agenda}} {{#each agenda}}

View file

@ -2,6 +2,6 @@ import { MemberViewModel } from "./member.models";
export interface ProtocolPresenceViewModel { export interface ProtocolPresenceViewModel {
memberId: number; memberId: number;
member: MemberViewModel; absent: boolean;
protocolId: number; protocolId: number;
} }