education controller, service, command

This commit is contained in:
Julian Krauser 2025-06-03 15:20:46 +02:00
parent 5368a96d0f
commit fded8a663a
29 changed files with 884 additions and 5 deletions

View file

@ -5,6 +5,7 @@ export interface CreateMemberCommand {
nameaffix: string; nameaffix: string;
birthdate: Date; birthdate: Date;
internalId?: string; internalId?: string;
note?: string;
} }
export interface UpdateMemberCommand { export interface UpdateMemberCommand {
@ -15,6 +16,7 @@ export interface UpdateMemberCommand {
nameaffix: string; nameaffix: string;
birthdate: Date; birthdate: Date;
internalId?: string; internalId?: string;
note?: string;
} }
export interface DeleteMemberCommand { export interface DeleteMemberCommand {

View file

@ -23,6 +23,7 @@ export default abstract class MemberCommandHandler {
nameaffix: createMember.nameaffix, nameaffix: createMember.nameaffix,
birthdate: createMember.birthdate, birthdate: createMember.birthdate,
internalId: createMember.internalId, internalId: createMember.internalId,
note: createMember.note,
}) })
.execute() .execute()
.then((result) => { .then((result) => {
@ -49,6 +50,7 @@ export default abstract class MemberCommandHandler {
nameaffix: updateMember.nameaffix, nameaffix: updateMember.nameaffix,
birthdate: updateMember.birthdate, birthdate: updateMember.birthdate,
internalId: updateMember.internalId, internalId: updateMember.internalId,
note: updateMember.note,
}) })
.where("id = :id", { id: updateMember.id }) .where("id = :id", { id: updateMember.id })
.execute() .execute()

View file

@ -0,0 +1,23 @@
export interface CreateMemberEducationCommand {
start: Date;
end?: Date;
note?: string;
place?: string;
memberId: string;
educationId: number;
}
export interface UpdateMemberEducationCommand {
id: number;
start: Date;
end?: Date;
note?: string;
place?: string;
memberId: string;
educationId: number;
}
export interface DeleteMemberEducationCommand {
id: number;
memberId: string;
}

View file

@ -0,0 +1,82 @@
import { dataSource } from "../../../data-source";
import { memberEducations } from "../../../entity/club/member/memberEducations";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import InternalException from "../../../exceptions/internalException";
import {
CreateMemberEducationCommand,
DeleteMemberEducationCommand,
UpdateMemberEducationCommand,
} from "./memberEducationCommand";
export default abstract class MemberEducationCommandHandler {
/**
* @description create memberEducation
* @param {CreateMemberEducationCommand} createMemberEducation
* @returns {Promise<number>}
*/
static async create(createMemberEducation: CreateMemberEducationCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(memberEducations)
.values({
note: createMemberEducation.note,
place: createMemberEducation.place,
start: createMemberEducation.start,
end: createMemberEducation.end,
memberId: createMemberEducation.memberId,
educationId: createMemberEducation.educationId,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new DatabaseActionException("CREATE", "memberEducation", err);
});
}
/**
* @description update memberEducation
* @param {UpdateMemberEducationCommand} updateMemberEducation
* @returns {Promise<void>}
*/
static async update(updateMemberEducation: UpdateMemberEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(memberEducations)
.set({
note: updateMemberEducation.note,
start: updateMemberEducation.start,
end: updateMemberEducation.end,
place: updateMemberEducation.place,
educationId: updateMemberEducation.educationId,
})
.where("id = :id", { id: updateMemberEducation.id })
.andWhere("memberId = :memberId", { memberId: updateMemberEducation.memberId })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("UPDATE", "memberEducation", err);
});
}
/**
* @description delete memberEducation
* @param {DeleteMemberEducationCommand} deleteMemberEducation
* @returns {Promise<void>}
*/
static async delete(deleteMemberEducation: DeleteMemberEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(memberEducations)
.where("id = :id", { id: deleteMemberEducation.id })
.andWhere("memberId = :memberId", { memberId: deleteMemberEducation.memberId })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("DELETE", "memberEducation", err);
});
}
}

View file

@ -0,0 +1,14 @@
export interface CreateEducationCommand {
education: string;
description?: string;
}
export interface UpdateEducationCommand {
id: number;
education: string;
description?: string;
}
export interface DeleteEducationCommand {
id: number;
}

View file

@ -0,0 +1,68 @@
import { dataSource } from "../../../data-source";
import { education } from "../../../entity/configuration/education";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import { CreateEducationCommand, DeleteEducationCommand, UpdateEducationCommand } from "./educationCommand";
export default abstract class EducationCommandHandler {
/**
* @description create education
* @param {CreateEducationCommand} createEducation
* @returns {Promise<number>}
*/
static async create(createEducation: CreateEducationCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(education)
.values({
education: createEducation.education,
description: createEducation.description,
})
.execute()
.then((result) => {
return result.identifiers[0].id;
})
.catch((err) => {
throw new DatabaseActionException("CREATE", "education", err);
});
}
/**
* @description update education
* @param {UpdateEducationCommand} updateEducation
* @returns {Promise<void>}
*/
static async update(updateEducation: UpdateEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(education)
.set({
education: updateEducation.education,
description: updateEducation.description,
})
.where("id = :id", { id: updateEducation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("UPDATE", "education", err);
});
}
/**
* @description delete education
* @param {DeleteEducationCommand} deleteEducation
* @returns {Promise<void>}
*/
static async delete(deleteEducation: DeleteEducationCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(education)
.where("id = :id", { id: deleteEducation.id })
.execute()
.then(() => {})
.catch((err) => {
throw new DatabaseActionException("DELETE", "education", err);
});
}
}

View file

@ -49,6 +49,14 @@ import {
import CommunicationCommandHandler from "../../../command/club/member/communicationCommandHandler"; import CommunicationCommandHandler from "../../../command/club/member/communicationCommandHandler";
import { PdfExport } from "../../../helpers/pdfExport"; import { PdfExport } from "../../../helpers/pdfExport";
import { PermissionModule } from "../../../type/permissionTypes"; import { PermissionModule } from "../../../type/permissionTypes";
import MemberEducationFactory from "../../../factory/admin/club/member/memberEducation";
import MemberEducationService from "../../../service/club/member/memberEducationService";
import {
CreateMemberEducationCommand,
DeleteMemberEducationCommand,
UpdateMemberEducationCommand,
} from "../../../command/club/member/memberEducationCommand";
import MemberEducationCommandHandler from "../../../command/club/member/memberEducationCommandHandler";
/** /**
* @description get all members * @description get all members
@ -276,6 +284,33 @@ export async function getQualificationByMemberAndRecord(req: Request, res: Respo
res.json(MemberQualificationFactory.mapToSingle(qualification)); res.json(MemberQualificationFactory.mapToSingle(qualification));
} }
/**
* @description get educations by member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getEducationsByMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
let educations = await MemberEducationService.getAll(memberId);
res.json(MemberEducationFactory.mapToBase(educations));
}
/**
* @description get education by member and record
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getEducationByMemberAndRecord(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const recordId = parseInt(req.params.id);
let education = await MemberEducationService.getById(memberId, recordId);
res.json(MemberEducationFactory.mapToSingle(education));
}
/** /**
* @description get executive positions by member * @description get executive positions by member
* @param req {Request} Express req object * @param req {Request} Express req object
@ -343,6 +378,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
const nameaffix = req.body.nameaffix; const nameaffix = req.body.nameaffix;
const birthdate = req.body.birthdate; const birthdate = req.body.birthdate;
const internalId = req.body.internalId || null; const internalId = req.body.internalId || null;
const note = req.body.note || null;
let createMember: CreateMemberCommand = { let createMember: CreateMemberCommand = {
salutationId, salutationId,
@ -351,6 +387,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
nameaffix, nameaffix,
birthdate, birthdate,
internalId, internalId,
note,
}; };
let memberId = await MemberCommandHandler.create(createMember); let memberId = await MemberCommandHandler.create(createMember);
@ -426,6 +463,33 @@ export async function addQualificationToMember(req: Request, res: Response): Pro
res.sendStatus(204); res.sendStatus(204);
} }
/**
* @description add education to member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function addEducationToMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const note = req.body.note;
const place = req.body.place;
const start = req.body.start;
const end = req.body.end || null;
const educationId = req.body.educationId;
let createMemberEducation: CreateMemberEducationCommand = {
note,
start,
end,
place,
memberId,
educationId,
};
await MemberEducationCommandHandler.create(createMemberEducation);
res.sendStatus(204);
}
/** /**
* @description add executive positions to member * @description add executive positions to member
* @param req {Request} Express req object * @param req {Request} Express req object
@ -504,6 +568,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
const nameaffix = req.body.nameaffix; const nameaffix = req.body.nameaffix;
const birthdate = req.body.birthdate; const birthdate = req.body.birthdate;
const internalId = req.body.internalId || null; const internalId = req.body.internalId || null;
const note = req.body.note || null;
let updateMember: UpdateMemberCommand = { let updateMember: UpdateMemberCommand = {
id: memberId, id: memberId,
@ -513,6 +578,7 @@ export async function updateMemberById(req: Request, res: Response): Promise<any
nameaffix, nameaffix,
birthdate, birthdate,
internalId, internalId,
note,
}; };
await MemberCommandHandler.update(updateMember); await MemberCommandHandler.update(updateMember);
@ -602,6 +668,35 @@ export async function updateQualificationOfMember(req: Request, res: Response):
res.sendStatus(204); res.sendStatus(204);
} }
/**
* @description update education of member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateEducationOfMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const recordId = parseInt(req.params.recordId);
const start = req.body.start;
const end = req.body.end || null;
const note = req.body.note;
const place = req.body.place;
const educationId = req.body.educationId;
let updateMemberEducation: UpdateMemberEducationCommand = {
id: recordId,
start,
end,
note,
place,
memberId,
educationId,
};
await MemberEducationCommandHandler.update(updateMemberEducation);
res.sendStatus(204);
}
/** /**
* @description update executive position of member * @description update executive position of member
* @param req {Request} Express req object * @param req {Request} Express req object
@ -742,6 +837,25 @@ export async function deleteQualificationOfMember(req: Request, res: Response):
res.sendStatus(204); res.sendStatus(204);
} }
/**
* @description delete education from member
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteEducationsOfMember(req: Request, res: Response): Promise<any> {
const memberId = req.params.memberId;
const recordId = parseInt(req.params.recordId);
let deleteMemberEducation: DeleteMemberEducationCommand = {
id: recordId,
memberId,
};
await MemberEducationCommandHandler.delete(deleteMemberEducation);
res.sendStatus(204);
}
/** /**
* @description delete executive position from member * @description delete executive position from member
* @param req {Request} Express req object * @param req {Request} Express req object

View file

@ -0,0 +1,91 @@
import { Request, Response } from "express";
import EducationService from "../../../service/configuration/education";
import EducationFactory from "../../../factory/admin/configuration/education";
import {
CreateEducationCommand,
DeleteEducationCommand,
UpdateEducationCommand,
} from "../../../command/configuration/education/educationCommand";
import EducationCommandHandler from "../../../command/configuration/education/educationCommandHandler";
/**
* @description get all educations
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getAllEducations(req: Request, res: Response): Promise<any> {
let educations = await EducationService.getAll();
res.json(EducationFactory.mapToBase(educations));
}
/**
* @description get education by id
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function getEducationById(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let education = await EducationService.getById(id);
res.json(EducationFactory.mapToSingle(education));
}
/**
* @description create new education
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function createEducation(req: Request, res: Response): Promise<any> {
const education = req.body.education;
const description = req.body.description;
let createEducation: CreateEducationCommand = {
education: education,
description: description,
};
await EducationCommandHandler.create(createEducation);
res.sendStatus(204);
}
/**
* @description update education
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function updateEducation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
const education = req.body.education;
const description = req.body.description;
let updateEducation: UpdateEducationCommand = {
id: id,
education: education,
description: description,
};
await EducationCommandHandler.update(updateEducation);
res.sendStatus(204);
}
/**
* @description delete education
* @param req {Request} Express req object
* @param res {Response} Express res object
* @returns {Promise<*>}
*/
export async function deleteEducation(req: Request, res: Response): Promise<any> {
const id = parseInt(req.params.id);
let deleteEducation: DeleteEducationCommand = {
id: id,
};
await EducationCommandHandler.delete(deleteEducation);
res.sendStatus(204);
}

View file

@ -58,6 +58,9 @@ import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLo
import { SettingsFromEnv_SET1745059495808 } from "./migrations/1745059495808-settingsFromEnv_set"; import { SettingsFromEnv_SET1745059495808 } from "./migrations/1745059495808-settingsFromEnv_set";
import { AddDateToNewsletter1748509435932 } from "./migrations/1748509435932-addDateToNewsletter"; import { AddDateToNewsletter1748509435932 } from "./migrations/1748509435932-addDateToNewsletter";
import { AddMembershipTotalView1748607842929 } from "./migrations/1748607842929-addMembershipTotalView"; import { AddMembershipTotalView1748607842929 } from "./migrations/1748607842929-addMembershipTotalView";
import { education } from "./entity/configuration/education";
import { memberEducations } from "./entity/club/member/memberEducations";
import { MemberExtendData1748953828644 } from "./migrations/1748953828644-memberExtendData";
configCheck(); configCheck();
@ -84,6 +87,7 @@ const dataSource = new DataSource({
communicationType, communicationType,
executivePosition, executivePosition,
membershipStatus, membershipStatus,
education,
qualification, qualification,
salutation, salutation,
member, member,
@ -91,6 +95,7 @@ const dataSource = new DataSource({
memberExecutivePositions, memberExecutivePositions,
memberQualifications, memberQualifications,
membership, membership,
memberEducations,
protocol, protocol,
protocolAgenda, protocolAgenda,
protocolDecision, protocolDecision,
@ -128,6 +133,7 @@ const dataSource = new DataSource({
UserLoginRoutine1746252454922, UserLoginRoutine1746252454922,
AddDateToNewsletter1748509435932, AddDateToNewsletter1748509435932,
AddMembershipTotalView1748607842929, AddMembershipTotalView1748607842929,
MemberExtendData1748953828644,
], ],
migrationsRun: true, migrationsRun: true,
migrationsTransactionMode: "each", migrationsTransactionMode: "each",

View file

@ -16,6 +16,7 @@ import { memberExecutivePositions } from "./memberExecutivePositions";
import { communication } from "./communication"; import { communication } from "./communication";
import { salutation } from "../../configuration/salutation"; import { salutation } from "../../configuration/salutation";
import { getTypeByORM } from "../../../migrations/ormHelper"; import { getTypeByORM } from "../../../migrations/ormHelper";
import { memberEducations } from "./memberEducations";
@Entity() @Entity()
export class member { export class member {
@ -37,6 +38,9 @@ export class member {
@Column({ type: "varchar", length: 255, unique: true, nullable: true }) @Column({ type: "varchar", length: 255, unique: true, nullable: true })
internalId?: string; internalId?: string;
@Column({ type: "varchar", length: 255, nullable: true })
note?: string;
@Column() @Column()
salutationId: number; salutationId: number;
@ -66,6 +70,9 @@ export class member {
@OneToMany(() => memberQualifications, (qualifications) => qualifications.member, { cascade: ["insert"] }) @OneToMany(() => memberQualifications, (qualifications) => qualifications.member, { cascade: ["insert"] })
qualifications: memberQualifications[]; qualifications: memberQualifications[];
@OneToMany(() => memberEducations, (educations) => educations.member, { cascade: ["insert"] })
educations: memberEducations[];
firstMembershipEntry?: membership; firstMembershipEntry?: membership;
lastMembershipEntry?: membership; lastMembershipEntry?: membership;
preferredCommunication?: Array<communication>; preferredCommunication?: Array<communication>;

View file

@ -0,0 +1,43 @@
import { Column, ColumnType, Entity, ManyToOne, PrimaryColumn } from "typeorm";
import { member } from "./member";
import { education } from "../../configuration/education";
import { getTypeByORM } from "../../../migrations/ormHelper";
@Entity()
export class memberEducations {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: getTypeByORM("date").type as ColumnType })
start: Date;
@Column({ type: getTypeByORM("date").type as ColumnType, nullable: true })
end?: Date;
@Column({ type: "varchar", length: 255, nullable: true })
note?: string;
@Column({ type: "varchar", length: 255, nullable: true })
place?: string;
@Column()
memberId: string;
@Column()
educationId: number;
@ManyToOne(() => member, (member) => member.awards, {
nullable: false,
onDelete: "CASCADE",
onUpdate: "RESTRICT",
})
member: member;
@ManyToOne(() => education, (education) => education.members, {
nullable: false,
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
cascade: ["insert"],
})
education: education;
}

View file

@ -0,0 +1,17 @@
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
import { memberEducations } from "../club/member/memberEducations";
@Entity()
export class education {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: "varchar", length: 255, unique: true })
education: string;
@Column({ type: "varchar", length: 255, nullable: true })
description?: string;
@OneToMany(() => memberEducations, (memberEducations) => memberEducations.education)
members: memberEducations[];
}

View file

@ -20,7 +20,8 @@ export default abstract class MemberFactory {
lastname: record?.lastname, lastname: record?.lastname,
nameaffix: record?.nameaffix, nameaffix: record?.nameaffix,
birthdate: record?.birthdate, birthdate: record?.birthdate,
internalId: record.internalId, internalId: record?.internalId,
note: record?.note,
firstMembershipEntry: record?.firstMembershipEntry firstMembershipEntry: record?.firstMembershipEntry
? MembershipFactory.mapToSingle(record.firstMembershipEntry) ? MembershipFactory.mapToSingle(record.firstMembershipEntry)
: null, : null,

View file

@ -0,0 +1,30 @@
import { memberEducations } from "../../../../entity/club/member/memberEducations";
import { MemberEducationViewModel } from "../../../../viewmodel/admin/club/member/memberEducation.models";
export default abstract class MemberEducationFactory {
/**
* @description map record to memberEducation
* @param {memberEducation} record
* @returns {MemberEducationViewModel}
*/
public static mapToSingle(record: memberEducations): MemberEducationViewModel {
return {
id: record.id,
start: record.start,
end: record.end,
note: record.note,
place: record.place,
education: record.education.education,
educationId: record.education.id,
};
}
/**
* @description map records to memberEducation
* @param {Array<memberEducation>} records
* @returns {Array<MemberEducationViewModel>}
*/
public static mapToBase(records: Array<memberEducations>): Array<MemberEducationViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -0,0 +1,26 @@
import { education } from "../../../entity/configuration/education";
import { EducationViewModel } from "../../../viewmodel/admin/configuration/education.models";
export default abstract class EducationFactory {
/**
* @description map record to education
* @param {education} record
* @returns {AwardViewModel}
*/
public static mapToSingle(record: education): EducationViewModel {
return {
id: record.id,
education: record.education,
description: record.description,
};
}
/**
* @description map records to education
* @param {Array<education>} records
* @returns {Array<AwardViewModel>}
*/
public static mapToBase(records: Array<education>): Array<EducationViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -8,6 +8,7 @@ import DatabaseActionException from "../exceptions/databaseActionException";
import { availableTemplates } from "../type/templateTypes"; import { availableTemplates } from "../type/templateTypes";
import SettingHelper from "./settingsHelper"; import SettingHelper from "./settingsHelper";
import { LoginRoutineEnum } from "../enums/loginRoutineEnum"; import { LoginRoutineEnum } from "../enums/loginRoutineEnum";
import { education } from "../entity/configuration/education";
export type BackupSection = export type BackupSection =
| "member" | "member"
@ -55,6 +56,7 @@ export default abstract class BackupHelper {
"member_executive_positions", "member_executive_positions",
"membership", "membership",
"communication", "communication",
"member_educations",
], ],
memberBase: [ memberBase: [
"award", "award",
@ -63,6 +65,7 @@ export default abstract class BackupHelper {
"membership_status", "membership_status",
"communication_type", "communication_type",
"salutation", "salutation",
"education",
], ],
protocol: [ protocol: [
"protocol", "protocol",
@ -246,6 +249,8 @@ export default abstract class BackupHelper {
.leftJoin("positions.executivePosition", "executivePosition") .leftJoin("positions.executivePosition", "executivePosition")
.leftJoin("member.qualifications", "qualifications") .leftJoin("member.qualifications", "qualifications")
.leftJoin("qualifications.qualification", "qualification") .leftJoin("qualifications.qualification", "qualification")
.leftJoin("member.educations", "educations")
.leftJoin("educations.education", "education")
.select([ .select([
...(collectIds ? ["member.id"] : []), ...(collectIds ? ["member.id"] : []),
"member.firstname", "member.firstname",
@ -253,6 +258,7 @@ export default abstract class BackupHelper {
"member.nameaffix", "member.nameaffix",
"member.birthdate", "member.birthdate",
"member.internalId", "member.internalId",
"member.note",
]) ])
.addSelect(["salutation.salutation"]) .addSelect(["salutation.salutation"])
.addSelect([ .addSelect([
@ -280,6 +286,14 @@ export default abstract class BackupHelper {
"qualification.qualification", "qualification.qualification",
"qualification.description", "qualification.description",
]) ])
.addSelect([
"educations.start",
"educations.end",
"educations.place",
"educations.note",
"education.education",
"education.description",
])
.getMany(); .getMany();
} }
private static async getMemberBase(): Promise<{ [key: string]: Array<any> }> { private static async getMemberBase(): Promise<{ [key: string]: Array<any> }> {
@ -294,6 +308,7 @@ export default abstract class BackupHelper {
qualification: await dataSource qualification: await dataSource
.getRepository("qualification") .getRepository("qualification")
.find({ select: { qualification: true, description: true } }), .find({ select: { qualification: true, description: true } }),
education: await dataSource.getRepository("education").find({ select: { education: true, description: true } }),
}; };
} }
private static async getProtocol(collectIds: boolean): Promise<Array<any>> { private static async getProtocol(collectIds: boolean): Promise<Array<any>> {
@ -344,6 +359,7 @@ export default abstract class BackupHelper {
"newsletter.newsletterText", "newsletter.newsletterText",
"newsletter.newsletterSignatur", "newsletter.newsletterSignatur",
"newsletter.isSent", "newsletter.isSent",
"newsletter.createdAt",
]) ])
.addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"]) .addSelect(["dates.calendarId", "dates.diffTitle", "dates.diffDescription"])
.addSelect(["recipients.memberId"]) .addSelect(["recipients.memberId"])
@ -534,6 +550,13 @@ export default abstract class BackupHelper {
.map((d) => ({ ...d, id: undefined })), .map((d) => ({ ...d, id: undefined })),
"qualification" "qualification"
), ),
education: uniqBy(
data
.map((d) => (d.education ?? []).map((c: any) => c.education))
.flat()
.map((d) => ({ ...d, id: undefined })),
"education"
),
}); });
let salutation = await this.transactionManager.getRepository("salutation").find(); let salutation = await this.transactionManager.getRepository("salutation").find();
@ -541,6 +564,7 @@ export default abstract class BackupHelper {
let membership = await this.transactionManager.getRepository("membership_status").find(); let membership = await this.transactionManager.getRepository("membership_status").find();
let award = await this.transactionManager.getRepository("award").find(); let award = await this.transactionManager.getRepository("award").find();
let qualification = await this.transactionManager.getRepository("qualification").find(); let qualification = await this.transactionManager.getRepository("qualification").find();
let education = await this.transactionManager.getRepository("education").find();
let position = await this.transactionManager.getRepository("executive_position").find(); let position = await this.transactionManager.getRepository("executive_position").find();
let dataWithMappedIds = data.map((d) => ({ let dataWithMappedIds = data.map((d) => ({
...d, ...d,
@ -583,6 +607,13 @@ export default abstract class BackupHelper {
id: qualification.find((iq) => iq.qualification == q.qualification.qualification)?.id ?? undefined, id: qualification.find((iq) => iq.qualification == q.qualification.qualification)?.id ?? undefined,
}, },
})), })),
educations: (d.educations ?? []).map((e: any) => ({
...e,
education: {
...e.education,
id: education.find((id) => id.education == e.education.education)?.id ?? undefined,
},
})),
})); }));
await this.transactionManager.getRepository("member").save(dataWithMappedIds); await this.transactionManager.getRepository("member").save(dataWithMappedIds);
} }
@ -593,6 +624,7 @@ export default abstract class BackupHelper {
let award = await this.transactionManager.getRepository("award").find(); let award = await this.transactionManager.getRepository("award").find();
let qualification = await this.transactionManager.getRepository("qualification").find(); let qualification = await this.transactionManager.getRepository("qualification").find();
let position = await this.transactionManager.getRepository("executive_position").find(); let position = await this.transactionManager.getRepository("executive_position").find();
let education = await this.transactionManager.getRepository("education").find();
await this.transactionManager await this.transactionManager
.createQueryBuilder() .createQueryBuilder()
@ -634,10 +666,19 @@ export default abstract class BackupHelper {
.insert() .insert()
.into("qualification") .into("qualification")
.values( .values(
(data?.["qualification"] ?? []).filter((d) => !qualification.map((q) => q.award).includes(d.qualification)) (data?.["qualification"] ?? []).filter(
(d) => !qualification.map((q) => q.qualification).includes(d.qualification)
)
) )
.orIgnore() .orIgnore()
.execute(); .execute();
await this.transactionManager
.createQueryBuilder()
.insert()
.into("education")
.values((data?.["education"] ?? []).filter((d) => !education.map((q) => q.education).includes(d.education)))
.orIgnore()
.execute();
} }
private static async setProtocol(data: Array<any>, collectedIds: boolean): Promise<void> { private static async setProtocol(data: Array<any>, collectedIds: boolean): Promise<void> {
let members = await this.transactionManager.getRepository("member").find(); let members = await this.transactionManager.getRepository("member").find();

View file

@ -17,6 +17,7 @@ export default abstract class DynamicQueryBuilder {
"memberAwards", "memberAwards",
"memberExecutivePositions", "memberExecutivePositions",
"memberQualifications", "memberQualifications",
"memberEducations",
"membership", "membership",
"memberView", "memberView",
"memberExecutivePositionsView", "memberExecutivePositionsView",
@ -293,7 +294,12 @@ export default abstract class DynamicQueryBuilder {
results.forEach((res) => { results.forEach((res) => {
if (typeof value === "object" && value instanceof Date) { if (typeof value === "object" && value instanceof Date) {
res[newKey] = new Date(value).toISOString(); res[newKey] = new Date(value).toISOString();
} else if (typeof value === "object" && !Array.isArray(value) && !(value instanceof Buffer)) { } else if (
typeof value === "object" &&
!Array.isArray(value) &&
!(value instanceof Buffer) &&
value !== null
) {
let string = ""; let string = "";
for (const key of Object.keys(value)) { for (const key of Object.keys(value)) {
string += `${value[key]} ${key} `; string += `${value[key]} ${key} `;
@ -302,7 +308,7 @@ export default abstract class DynamicQueryBuilder {
// JSON.stringify(value).replace(/["\\{}]/g, "").replaceAll(",", ", "); // JSON.stringify(value).replace(/["\\{}]/g, "").replaceAll(",", ", ");
} else if (String(value) != "undefined") { } else if (String(value) != "undefined") {
res[newKey] = String(value); res[newKey] = value !== null ? String(value) : "";
} }
}); });
} }

View file

@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
import {
education_table,
member_educations_table,
member_view_mysql,
member_view_postgres,
member_view_sqlite,
} from "./baseSchemaTables/member";
import { getDefaultByORM, getTypeByORM } from "./ormHelper";
import { DB_TYPE } from "../env.defaults";
export class MemberExtendData1748953828644 implements MigrationInterface {
name = "MemberExtendData1748953828644";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(education_table, true, true, true);
await queryRunner.createTable(member_educations_table, true, true, true);
await queryRunner.addColumn(
"member",
new TableColumn({ name: "note", ...getTypeByORM("varchar", true), default: getDefaultByORM("null") })
);
await queryRunner.dropView("member_view");
if (DB_TYPE == "postgres") await queryRunner.createView(member_view_postgres, true);
else if (DB_TYPE == "mysql") await queryRunner.createView(member_view_mysql, true);
else if (DB_TYPE == "sqlite") await queryRunner.createView(member_view_sqlite, true);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable(member_educations_table, true, true, true);
await queryRunner.dropTable(education_table, true, true, true);
await queryRunner.dropColumn("member", "note");
}
}

View file

@ -51,6 +51,15 @@ export const qualification_table = new Table({
], ],
}); });
export const education_table = new Table({
name: "education",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "education", ...getTypeByORM("varchar"), isUnique: true },
{ name: "description", ...getTypeByORM("varchar"), isNullable: true },
],
});
/** member and relations */ /** member and relations */
export const member_table = new Table({ export const member_table = new Table({
name: "member", name: "member",
@ -222,6 +231,35 @@ export const member_communication_table = new Table({
], ],
}); });
export const member_educations_table = new Table({
name: "member_educations",
columns: [
{ name: "id", ...getTypeByORM("int"), ...isIncrementPrimary },
{ name: "start", ...getTypeByORM("date") },
{ name: "end", ...getTypeByORM("date", true), default: getDefaultByORM("null") },
{ name: "note", ...getTypeByORM("varchar"), isNullable: true },
{ name: "place", ...getTypeByORM("varchar"), isNullable: true },
{ name: "memberId", ...getTypeByORM("uuid") },
{ name: "educationId", ...getTypeByORM("int") },
],
foreignKeys: [
new TableForeignKey({
columnNames: ["memberId"],
referencedTableName: "member",
referencedColumnNames: ["id"],
onDelete: "CASCADE",
onUpdate: "RESTRICT",
}),
new TableForeignKey({
columnNames: ["educationId"],
referencedTableName: "education",
referencedColumnNames: ["id"],
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
}),
],
});
/** views */ /** views */
export const member_view_mysql = new View({ export const member_view_mysql = new View({
name: "member_view", name: "member_view",
@ -229,6 +267,7 @@ export const member_view_mysql = new View({
SELECT SELECT
\`member\`.\`id\` AS \`id\`, \`member\`.\`id\` AS \`id\`,
\`member\`.\`internalId\` AS \`internalId\`, \`member\`.\`internalId\` AS \`internalId\`,
\`member\`.\`note\` AS \`note\`,
\`member\`.\`firstname\` AS \`firstname\`, \`member\`.\`firstname\` AS \`firstname\`,
\`member\`.\`lastname\` AS \`lastname\`, \`member\`.\`lastname\` AS \`lastname\`,
\`member\`.\`nameaffix\` AS \`nameaffix\`, \`member\`.\`nameaffix\` AS \`nameaffix\`,
@ -258,6 +297,7 @@ export const member_view_postgres = new View({
SELECT SELECT
"member"."id" AS "id", "member"."id" AS "id",
"member"."internalId" AS "internalId", "member"."internalId" AS "internalId",
"member"."note" AS "note",
"member"."firstname" AS "firstname", "member"."firstname" AS "firstname",
"member"."lastname" AS "lastname", "member"."lastname" AS "lastname",
"member"."nameaffix" AS "nameaffix", "member"."nameaffix" AS "nameaffix",
@ -277,6 +317,7 @@ export const member_view_sqlite = new View({
SELECT SELECT
member.id AS id, member.id AS id,
member.internalId AS internalId, member.internalId AS internalId,
member.note AS note,
member.firstname AS firstname, member.firstname AS firstname,
member.lastname AS lastname, member.lastname AS lastname,
member.nameaffix AS nameaffix, member.nameaffix AS nameaffix,

View file

@ -2,12 +2,14 @@ import express, { Request, Response } from "express";
import { import {
addAwardToMember, addAwardToMember,
addCommunicationToMember, addCommunicationToMember,
addEducationToMember,
addExecutivePositionToMember, addExecutivePositionToMember,
addMembershipToMember, addMembershipToMember,
addQualificationToMember, addQualificationToMember,
createMember, createMember,
deleteAwardOfMember, deleteAwardOfMember,
deleteCommunicationOfMember, deleteCommunicationOfMember,
deleteEducationsOfMember,
deleteExecutivePositionOfMember, deleteExecutivePositionOfMember,
deleteMemberById, deleteMemberById,
deleteMembershipOfMember, deleteMembershipOfMember,
@ -17,6 +19,8 @@ import {
getAwardsByMember, getAwardsByMember,
getCommunicationByMemberAndRecord, getCommunicationByMemberAndRecord,
getCommunicationsByMember, getCommunicationsByMember,
getEducationByMemberAndRecord,
getEducationsByMember,
getExecutivePositionByMemberAndRecord, getExecutivePositionByMemberAndRecord,
getExecutivePositionsByMember, getExecutivePositionsByMember,
getMemberById, getMemberById,
@ -32,6 +36,7 @@ import {
getQualificationsByMember, getQualificationsByMember,
updateAwardOfMember, updateAwardOfMember,
updateCommunicationOfMember, updateCommunicationOfMember,
updateEducationOfMember,
updateExecutivePositionOfMember, updateExecutivePositionOfMember,
updateMemberById, updateMemberById,
updateMembershipOfMember, updateMembershipOfMember,
@ -97,6 +102,14 @@ router.get("/:memberId/qualification/:id", async (req: Request, res: Response) =
await getQualificationByMemberAndRecord(req, res); await getQualificationByMemberAndRecord(req, res);
}); });
router.get("/:memberId/educations", async (req: Request, res: Response) => {
await getEducationsByMember(req, res);
});
router.get("/:memberId/education/:id", async (req: Request, res: Response) => {
await getEducationByMemberAndRecord(req, res);
});
router.get("/:memberId/positions", async (req: Request, res: Response) => { router.get("/:memberId/positions", async (req: Request, res: Response) => {
await getExecutivePositionsByMember(req, res); await getExecutivePositionsByMember(req, res);
}); });
@ -145,6 +158,14 @@ router.post(
} }
); );
router.post(
"/:memberId/education",
PermissionHelper.passCheckMiddleware("create", "club", "member"),
async (req: Request, res: Response) => {
await addEducationToMember(req, res);
}
);
router.post( router.post(
"/:memberId/position", "/:memberId/position",
PermissionHelper.passCheckMiddleware("create", "club", "member"), PermissionHelper.passCheckMiddleware("create", "club", "member"),
@ -193,6 +214,14 @@ router.patch(
} }
); );
router.patch(
"/:memberId/education/:recordId",
PermissionHelper.passCheckMiddleware("update", "club", "member"),
async (req: Request, res: Response) => {
await updateEducationOfMember(req, res);
}
);
router.patch( router.patch(
"/:memberId/position/:recordId", "/:memberId/position/:recordId",
PermissionHelper.passCheckMiddleware("update", "club", "member"), PermissionHelper.passCheckMiddleware("update", "club", "member"),
@ -241,6 +270,14 @@ router.delete(
} }
); );
router.delete(
"/:memberId/education/:recordId",
PermissionHelper.passCheckMiddleware("delete", "club", "member"),
async (req: Request, res: Response) => {
await deleteEducationsOfMember(req, res);
}
);
router.delete( router.delete(
"/:memberId/position/:recordId", "/:memberId/position/:recordId",
PermissionHelper.passCheckMiddleware("delete", "club", "member"), PermissionHelper.passCheckMiddleware("delete", "club", "member"),

View file

@ -0,0 +1,45 @@
import express, { Request, Response } from "express";
import {
createEducation,
deleteEducation,
getAllEducations,
getEducationById,
updateEducation,
} from "../../../controller/admin/configuration/educationController";
import PermissionHelper from "../../../helpers/permissionHelper";
var router = express.Router({ mergeParams: true });
router.get("/", async (req: Request, res: Response) => {
await getAllEducations(req, res);
});
router.get("/:id", async (req: Request, res: Response) => {
await getEducationById(req, res);
});
router.post(
"/",
PermissionHelper.passCheckMiddleware("create", "configuration", "education"),
async (req: Request, res: Response) => {
await createEducation(req, res);
}
);
router.patch(
"/:id",
PermissionHelper.passCheckMiddleware("update", "configuration", "education"),
async (req: Request, res: Response) => {
await updateEducation(req, res);
}
);
router.delete(
"/:id",
PermissionHelper.passCheckMiddleware("delete", "configuration", "education"),
async (req: Request, res: Response) => {
await deleteEducation(req, res);
}
);
export default router;

View file

@ -7,6 +7,7 @@ import communicationType from "./configuration/communicationType";
import executivePosition from "./configuration/executivePosition"; import executivePosition from "./configuration/executivePosition";
import membershipStatus from "./configuration/membershipStatus"; import membershipStatus from "./configuration/membershipStatus";
import qualification from "./configuration/qualification"; import qualification from "./configuration/qualification";
import education from "./configuration/education";
import salutation from "./configuration/salutation"; import salutation from "./configuration/salutation";
import calendarType from "./configuration/calendarType"; import calendarType from "./configuration/calendarType";
import queryStore from "./configuration/queryStore"; import queryStore from "./configuration/queryStore";
@ -70,6 +71,14 @@ router.use(
]), ]),
qualification qualification
); );
router.use(
"/education",
PermissionHelper.passCheckSomeMiddleware([
{ requiredPermission: "read", section: "configuration", module: "education" },
{ requiredPermission: "read", section: "club", module: "member" },
]),
education
);
router.use( router.use(
"/salutation", "/salutation",
PermissionHelper.passCheckSomeMiddleware([ PermissionHelper.passCheckSomeMiddleware([

View file

@ -0,0 +1,49 @@
import { dataSource } from "../../../data-source";
import { memberEducations } from "../../../entity/club/member/memberEducations";
import DatabaseActionException from "../../../exceptions/databaseActionException";
import InternalException from "../../../exceptions/internalException";
export default abstract class MemberEducationService {
/**
* @description get all by member id
* @param {string} memberId
* @returns {Promise<Array<memberEducations>>}
*/
static async getAll(memberId: string): Promise<Array<memberEducations>> {
return await dataSource
.getRepository(memberEducations)
.createQueryBuilder("memberEducations")
.leftJoinAndSelect("memberEducations.education", "education")
.where("memberEducations.memberId = :memberId", { memberId: memberId })
.orderBy("education.education", "ASC")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "memberEducations", err);
});
}
/**
* @description get by memberId and recordId
* @param {string} memberId
* @param {number} recordId
* @returns {Promise<Array<member>>}
*/
static async getById(memberId: string, recordId: number): Promise<memberEducations> {
return await dataSource
.getRepository(memberEducations)
.createQueryBuilder("memberEducations")
.leftJoinAndSelect("memberEducations.education", "education")
.where("memberEducations.memberId = :memberId", { memberId: memberId })
.andWhere("memberEducations.id = :recordId", { recordId: recordId })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "memberEducations", err);
});
}
}

View file

@ -0,0 +1,41 @@
import { dataSource } from "../../data-source";
import { education } from "../../entity/configuration/education";
import DatabaseActionException from "../../exceptions/databaseActionException";
export default abstract class EducationService {
/**
* @description get all educations
* @returns {Promise<Array<education>>}
*/
static async getAll(): Promise<Array<education>> {
return await dataSource
.getRepository(education)
.createQueryBuilder("education")
.orderBy("education", "ASC")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "education", err);
});
}
/**
* @description get education by id
* @returns {Promise<education>}
*/
static async getById(id: number): Promise<education> {
return await dataSource
.getRepository(education)
.createQueryBuilder("education")
.where("education.id = :id", { id: id })
.getOneOrFail()
.then((res) => {
return res;
})
.catch((err) => {
throw new DatabaseActionException("SELECT", "education", err);
});
}
}

View file

@ -13,6 +13,7 @@ export type PermissionModule =
| "communication_type" | "communication_type"
| "membership_status" | "membership_status"
| "salutation" | "salutation"
| "education"
| "calendar_type" | "calendar_type"
| "user" | "user"
| "role" | "role"
@ -70,6 +71,7 @@ export const permissionModules: Array<PermissionModule> = [
"communication_type", "communication_type",
"membership_status", "membership_status",
"salutation", "salutation",
"education",
"calendar_type", "calendar_type",
"user", "user",
"role", "role",
@ -91,6 +93,7 @@ export const sectionsAndModules: SectionsAndModulesObject = {
"communication_type", "communication_type",
"membership_status", "membership_status",
"salutation", "salutation",
"education",
"calendar_type", "calendar_type",
"query_store", "query_store",
"template", "template",

View file

@ -15,6 +15,7 @@ export interface MemberViewModel {
sendNewsletter?: CommunicationViewModel; sendNewsletter?: CommunicationViewModel;
smsAlarming?: Array<CommunicationViewModel>; smsAlarming?: Array<CommunicationViewModel>;
preferredCommunication?: Array<CommunicationViewModel>; preferredCommunication?: Array<CommunicationViewModel>;
note?: string;
} }
export interface MemberStatisticsViewModel { export interface MemberStatisticsViewModel {

View file

@ -0,0 +1,26 @@
export interface MemberEducationViewModel {
id: number;
start: Date;
end?: Date;
place?: string;
note?: string;
education: string;
educationId: number;
}
export interface CreateMemberEducationViewModel {
start: Date;
end?: Date;
place?: string;
note?: string;
educationId: number;
}
export interface UpdateMemberEducationViewModel {
id: number;
start: Date;
end?: Date;
place?: string;
note?: string;
educationId: number;
}

View file

@ -0,0 +1,16 @@
export interface EducationViewModel {
id: number;
education: string;
description: string | null;
}
export interface CreateEducationViewModel {
education: string;
description: string | null;
}
export interface UpdateEducationViewModel {
id: number;
education: string;
description: string | null;
}

View file

@ -41,7 +41,8 @@ if (DB_TYPE == "postgres") {
.getRepository(member) .getRepository(member)
.createQueryBuilder("member") .createQueryBuilder("member")
.select("member.id", "id") .select("member.id", "id")
.select("member.internalId", "internalId") .addSelect("member.internalId", "internalId")
.addSelect("member.note", "note")
.addSelect("member.firstname", "firstname") .addSelect("member.firstname", "firstname")
.addSelect("member.lastname", "lastname") .addSelect("member.lastname", "lastname")
.addSelect("member.nameaffix", "nameaffix") .addSelect("member.nameaffix", "nameaffix")
@ -62,6 +63,9 @@ export class memberView {
@ViewColumn() @ViewColumn()
internalId: string; internalId: string;
@ViewColumn()
note: string;
@ViewColumn() @ViewColumn()
firstname: string; firstname: string;