change: newsletter saving

This commit is contained in:
Julian Krauser 2025-01-31 11:40:59 +01:00
parent 7c08251ff6
commit fc694aa976
12 changed files with 173 additions and 132 deletions

View file

@ -1,6 +1,7 @@
export interface CreateCommunicationCommand { export interface CreateCommunicationCommand {
preferred: boolean; preferred: boolean;
isSMSAlarming: boolean; isSMSAlarming: boolean;
isSendNewsletter: boolean;
mobile: string; mobile: string;
email: string; email: string;
postalCode: string; postalCode: string;
@ -16,6 +17,7 @@ export interface UpdateCommunicationCommand {
id: number; id: number;
preferred: boolean; preferred: boolean;
isSMSAlarming: boolean; isSMSAlarming: boolean;
isSendNewsletter: boolean;
mobile: string; mobile: string;
email: string; email: string;
postalCode: string; postalCode: string;

View file

@ -14,26 +14,44 @@ export default abstract class CommunicationCommandHandler {
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
static async create(createCommunication: CreateCommunicationCommand): Promise<number> { static async create(createCommunication: CreateCommunicationCommand): Promise<number> {
let insertId = -1;
return await dataSource return await dataSource
.createQueryBuilder() .transaction(async (manager) => {
.insert() await manager
.into(communication) .createQueryBuilder()
.values({ .insert()
preferred: createCommunication.preferred, .into(communication)
isSMSAlarming: createCommunication.isSMSAlarming, .values({
mobile: createCommunication.mobile, preferred: createCommunication.preferred,
email: createCommunication.email, isSMSAlarming: createCommunication.isSMSAlarming,
postalCode: createCommunication.postalCode, isSendNewsletter: createCommunication.isSendNewsletter,
city: createCommunication.city, mobile: createCommunication.mobile,
street: createCommunication.street, email: createCommunication.email,
streetNumber: createCommunication.streetNumber, postalCode: createCommunication.postalCode,
streetNumberAddition: createCommunication.streetNumberAddition, city: createCommunication.city,
memberId: createCommunication.memberId, street: createCommunication.street,
typeId: createCommunication.typeId, streetNumber: createCommunication.streetNumber,
streetNumberAddition: createCommunication.streetNumberAddition,
memberId: createCommunication.memberId,
typeId: createCommunication.typeId,
})
.execute()
.then((result) => {
insertId = result.identifiers[0].id;
});
await manager
.createQueryBuilder()
.update(communication)
.set({
isSendNewsletter: false,
})
.where("memberId = :memberId", { memberId: createCommunication.memberId })
.andWhere("id <> :id", { id: insertId })
.execute();
}) })
.execute() .then(() => {
.then((result) => { return insertId;
return result.identifiers[0].id;
}) })
.catch((err) => { .catch((err) => {
throw new InternalException("Failed creating communication", err); throw new InternalException("Failed creating communication", err);
@ -47,22 +65,36 @@ export default abstract class CommunicationCommandHandler {
*/ */
static async update(updateCommunication: UpdateCommunicationCommand): Promise<void> { static async update(updateCommunication: UpdateCommunicationCommand): Promise<void> {
return await dataSource return await dataSource
.createQueryBuilder() .transaction(async (manager) => {
.update(communication) await manager
.set({ .createQueryBuilder()
preferred: updateCommunication.preferred, .update(communication)
isSMSAlarming: updateCommunication.isSMSAlarming, .set({
mobile: updateCommunication.mobile, preferred: updateCommunication.preferred,
email: updateCommunication.email, isSMSAlarming: updateCommunication.isSMSAlarming,
postalCode: updateCommunication.postalCode, isSendNewsletter: updateCommunication.isSendNewsletter,
city: updateCommunication.city, mobile: updateCommunication.mobile,
street: updateCommunication.street, email: updateCommunication.email,
streetNumber: updateCommunication.streetNumber, postalCode: updateCommunication.postalCode,
streetNumberAddition: updateCommunication.streetNumberAddition, city: updateCommunication.city,
street: updateCommunication.street,
streetNumber: updateCommunication.streetNumber,
streetNumberAddition: updateCommunication.streetNumberAddition,
})
.where("id = :id", { id: updateCommunication.id })
.andWhere("memberId = :memberId", { memberId: updateCommunication.memberId })
.execute();
await manager
.createQueryBuilder()
.update(communication)
.set({
isSendNewsletter: false,
})
.where("memberId = :memberId", { memberId: updateCommunication.memberId })
.andWhere("id <> :id", { id: updateCommunication.id })
.execute();
}) })
.where("id = :id", { id: updateCommunication.id })
.andWhere("memberId = :memberId", { memberId: updateCommunication.memberId })
.execute()
.then(() => {}) .then(() => {})
.catch((err) => { .catch((err) => {
throw new InternalException("Failed updating communication", err); throw new InternalException("Failed updating communication", err);

View file

@ -17,11 +17,6 @@ export interface UpdateMemberCommand {
internalId?: string; internalId?: string;
} }
export interface UpdateMemberNewsletterCommand {
id: number;
communicationId: number;
}
export interface DeleteMemberCommand { export interface DeleteMemberCommand {
id: number; id: number;
} }

View file

@ -2,12 +2,7 @@ import { dataSource } from "../../../data-source";
import { communication } from "../../../entity/club/member/communication"; import { communication } from "../../../entity/club/member/communication";
import { member } from "../../../entity/club/member/member"; import { member } from "../../../entity/club/member/member";
import InternalException from "../../../exceptions/internalException"; import InternalException from "../../../exceptions/internalException";
import { import { CreateMemberCommand, DeleteMemberCommand, UpdateMemberCommand } from "./memberCommand";
CreateMemberCommand,
DeleteMemberCommand,
UpdateMemberCommand,
UpdateMemberNewsletterCommand,
} from "./memberCommand";
export default abstract class MemberCommandHandler { export default abstract class MemberCommandHandler {
/** /**
@ -68,51 +63,6 @@ export default abstract class MemberCommandHandler {
}); });
} }
/**
* @description update member newsletter
* @param {UpdateMemberCommand} updateMember
* @returns {Promise<void>}
*/
static async updateNewsletter(updateMember: UpdateMemberNewsletterCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(member)
.set({
sendNewsletter: await dataSource
.getRepository(communication)
.createQueryBuilder("communication")
.where("id = :id", { id: updateMember.communicationId })
.andWhere("memberId = :memberId", { memberId: updateMember.id })
.getOneOrFail(),
})
.where("id = :id", { id: updateMember.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException(`Failed updating member`, err);
});
}
/**
* @description update member newsletter to unset
* @param {number} memberId
* @returns {Promise<void>}
*/
static async unsetNewsletter(memberId: number): Promise<void> {
return await dataSource
.createQueryBuilder()
.update(member)
.set({
sendNewsletter: null,
})
.where("id = :id", { id: memberId })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed updating member", err);
});
}
/** /**
* @description delete member * @description delete member
* @param {DeleteMemberCommand} deleteMember * @param {DeleteMemberCommand} deleteMember

View file

@ -15,7 +15,6 @@ import {
CreateMemberCommand, CreateMemberCommand,
DeleteMemberCommand, DeleteMemberCommand,
UpdateMemberCommand, UpdateMemberCommand,
UpdateMemberNewsletterCommand,
} from "../../../command/club/member/memberCommand"; } from "../../../command/club/member/memberCommand";
import MemberCommandHandler from "../../../command/club/member/memberCommandHandler"; import MemberCommandHandler from "../../../command/club/member/memberCommandHandler";
import { import {
@ -290,7 +289,7 @@ export async function createMember(req: Request, res: Response): Promise<any> {
const lastname = req.body.lastname; const lastname = req.body.lastname;
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;
let createMember: CreateMemberCommand = { let createMember: CreateMemberCommand = {
salutationId, salutationId,
@ -409,6 +408,7 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
const memberId = parseInt(req.params.memberId); const memberId = parseInt(req.params.memberId);
const preferred = req.body.preferred; const preferred = req.body.preferred;
const isSMSAlarming = req.body.isSMSAlarming; const isSMSAlarming = req.body.isSMSAlarming;
const isSendNewsletter = req.body.isNewsletterMain;
const mobile = req.body.mobile; const mobile = req.body.mobile;
const email = req.body.email; const email = req.body.email;
const postalCode = req.body.postalCode; const postalCode = req.body.postalCode;
@ -417,11 +417,11 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
const streetNumber = req.body.streetNumber; const streetNumber = req.body.streetNumber;
const streetNumberAddition = req.body.streetNumberAddition; const streetNumberAddition = req.body.streetNumberAddition;
const typeId = req.body.typeId; const typeId = req.body.typeId;
const isNewsletterMain = req.body.isNewsletterMain;
let createCommunication: CreateCommunicationCommand = { let createCommunication: CreateCommunicationCommand = {
preferred, preferred,
isSMSAlarming, isSMSAlarming,
isSendNewsletter,
mobile, mobile,
email, email,
postalCode, postalCode,
@ -434,14 +434,6 @@ export async function addCommunicationToMember(req: Request, res: Response): Pro
}; };
let id = await CommunicationCommandHandler.create(createCommunication); let id = await CommunicationCommandHandler.create(createCommunication);
if (isNewsletterMain) {
let updateNewsletter: UpdateMemberNewsletterCommand = {
id: memberId,
communicationId: id,
};
await MemberCommandHandler.updateNewsletter(updateNewsletter);
}
res.sendStatus(204); res.sendStatus(204);
} }
@ -595,6 +587,7 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
const recordId = parseInt(req.params.recordId); const recordId = parseInt(req.params.recordId);
const preferred = req.body.preferred; const preferred = req.body.preferred;
const isSMSAlarming = req.body.isSMSAlarming; const isSMSAlarming = req.body.isSMSAlarming;
const isSendNewsletter = req.body.isNewsletterMain;
const mobile = req.body.mobile; const mobile = req.body.mobile;
const email = req.body.email; const email = req.body.email;
const postalCode = req.body.postalCode; const postalCode = req.body.postalCode;
@ -602,12 +595,12 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
const street = req.body.street; const street = req.body.street;
const streetNumber = req.body.streetNumber; const streetNumber = req.body.streetNumber;
const streetNumberAddition = req.body.streetNumberAddition; const streetNumberAddition = req.body.streetNumberAddition;
const isNewsletterMain = req.body.isNewsletterMain;
let updateCommunication: UpdateCommunicationCommand = { let updateCommunication: UpdateCommunicationCommand = {
id: recordId, id: recordId,
preferred, preferred,
isSMSAlarming, isSMSAlarming,
isSendNewsletter,
mobile, mobile,
email, email,
postalCode, postalCode,
@ -619,18 +612,6 @@ export async function updateCommunicationOfMember(req: Request, res: Response):
}; };
await CommunicationCommandHandler.update(updateCommunication); await CommunicationCommandHandler.update(updateCommunication);
let currentUserNewsletterMain = await MemberService.getNewsletterById(memberId);
if (isNewsletterMain) {
let updateNewsletter: UpdateMemberNewsletterCommand = {
id: memberId,
communicationId: recordId,
};
await MemberCommandHandler.updateNewsletter(updateNewsletter);
} else if (currentUserNewsletterMain.sendNewsletter?.id == recordId) {
await MemberCommandHandler.unsetNewsletter(memberId);
}
res.sendStatus(204); res.sendStatus(204);
} }

View file

@ -74,6 +74,7 @@ import { AddWebapiTokens1737453096674 } from "./migrations/1737453096674-addweba
import { salutation } from "./entity/settings/salutation"; import { salutation } from "./entity/settings/salutation";
import { SalutationAsTable1737796878058 } from "./migrations/1737796878058-salutationAsTable"; import { SalutationAsTable1737796878058 } from "./migrations/1737796878058-salutationAsTable";
import { UpdateViews1737800468938 } from "./migrations/1737800468938-updateViews"; import { UpdateViews1737800468938 } from "./migrations/1737800468938-updateViews";
import { MoveSendNewsletterFlag1737816852011 } from "./migrations/1737816852011-moveSendNewsletterFlag";
const dataSource = new DataSource({ const dataSource = new DataSource({
type: DB_TYPE as any, type: DB_TYPE as any,
@ -158,6 +159,7 @@ const dataSource = new DataSource({
AddWebapiTokens1737453096674, AddWebapiTokens1737453096674,
SalutationAsTable1737796878058, SalutationAsTable1737796878058,
UpdateViews1737800468938, UpdateViews1737800468938,
MoveSendNewsletterFlag1737816852011,
], ],
migrationsRun: true, migrationsRun: true,
migrationsTransactionMode: "each", migrationsTransactionMode: "each",

View file

@ -13,6 +13,9 @@ export class communication {
@Column({ type: "boolean", default: false }) @Column({ type: "boolean", default: false })
isSMSAlarming: boolean; isSMSAlarming: boolean;
@Column({ type: "boolean", default: false })
isSendNewsletter: boolean;
@Column({ type: "varchar", length: 255, nullable: true }) @Column({ type: "varchar", length: 255, nullable: true })
mobile: string; mobile: string;

View file

@ -32,14 +32,6 @@ export class member {
@OneToMany(() => communication, (communications) => communications.member) @OneToMany(() => communication, (communications) => communications.member)
communications: communication[]; communications: communication[];
@OneToOne(() => communication, {
nullable: true,
onDelete: "SET NULL",
onUpdate: "RESTRICT",
})
@JoinColumn()
sendNewsletter?: communication;
@ManyToOne(() => salutation, (salutation) => salutation.members) @ManyToOne(() => salutation, (salutation) => salutation.members)
salutation: salutation; salutation: salutation;
@ -59,4 +51,5 @@ export class member {
lastMembershipEntry?: membership; lastMembershipEntry?: membership;
preferredCommunication?: Array<communication>; preferredCommunication?: Array<communication>;
smsAlarming?: Array<communication>; smsAlarming?: Array<communication>;
sendNewsletter?: communication;
} }

View file

@ -20,7 +20,7 @@ export default abstract class CommunicationFactory {
streetNumber: record.streetNumber, streetNumber: record.streetNumber,
streetNumberAddition: record.streetNumberAddition, streetNumberAddition: record.streetNumberAddition,
type: CommunicationTypeFactory.mapToSingle(record.type), type: CommunicationTypeFactory.mapToSingle(record.type),
isNewsletterMain: isMain ? isMain : record?.member?.sendNewsletter?.id == record.id, isNewsletterMain: record?.isSendNewsletter,
isSMSAlarming: record.isSMSAlarming, isSMSAlarming: record.isSMSAlarming,
}; };
} }

View file

@ -0,0 +1,71 @@
import { MigrationInterface, QueryRunner, TableColumn, TableForeignKey, TableIndex } from "typeorm";
import { communication } from "../entity/club/member/communication";
import { member } from "../entity/club/member/member";
export class MoveSendNewsletterFlag1737816852011 implements MigrationInterface {
name = "MoveSendNewsletterFlag1737816852011";
public async up(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("member");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("sendNewsletterId") !== -1);
await queryRunner.dropForeignKey("member", foreignKey);
await queryRunner.addColumn(
"communication",
new TableColumn({ name: "isSendNewsletter", type: "tinyint", isNullable: false, default: 0 })
);
// ! has to be sql. Else no data is returned.
const member_newsletter_send = await queryRunner.query("SELECT sendNewsletterId, id FROM `member` `member`");
for (let assigned of member_newsletter_send.map((mns: any) => ({
id: mns.id,
sendNewsletterId: mns.sendNewsletterId,
})) as Array<{ id: number; sendNewsletterId: number }>) {
await queryRunner.manager
.getRepository(communication)
.createQueryBuilder("communication")
.update({ isSendNewsletter: true })
.where({ memberId: assigned.id, id: assigned.sendNewsletterId })
.execute();
}
await queryRunner.dropColumn("member", "sendNewsletterId");
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
"member",
new TableColumn({ name: "sendNewsletterId", type: "int", isNullable: true, isUnique: true, default: null })
);
const member_newsletter_send = await queryRunner.manager
.getRepository(communication)
.createQueryBuilder("communication")
.where("communication.isSendNewsletter = :isSendNewsletter", { isSendNewsletter: true })
.getMany();
for (let assigned of member_newsletter_send.map((mns: any) => ({
id: mns.id,
memberId: mns.memberId,
})) as Array<{ id: number; memberId: number }>) {
await queryRunner.query("UPDATE `member` SET sendNewsletterId = ? WHERE id = ?", [
assigned.id,
assigned.memberId,
]);
}
await queryRunner.createForeignKey(
"member",
new TableForeignKey({
columnNames: ["sendNewsletterId"],
referencedColumnNames: ["id"],
referencedTableName: "communication",
onDelete: "SET NULL",
onUpdate: "RESTRICT",
})
);
await queryRunner.dropColumn("communication", "isSendNewsletter");
}
}

View file

@ -14,7 +14,6 @@ export default abstract class CommunicationService {
.createQueryBuilder("communication") .createQueryBuilder("communication")
.leftJoinAndSelect("communication.type", "communicationType") .leftJoinAndSelect("communication.type", "communicationType")
.leftJoinAndSelect("communication.member", "member") .leftJoinAndSelect("communication.member", "member")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.where("communication.memberId = :memberId", { memberId: memberId }) .where("communication.memberId = :memberId", { memberId: memberId })
.orderBy("communicationType.type", "ASC") .orderBy("communicationType.type", "ASC")
.getMany() .getMany()
@ -38,7 +37,6 @@ export default abstract class CommunicationService {
.createQueryBuilder("communication") .createQueryBuilder("communication")
.leftJoinAndSelect("communication.type", "communicationType") .leftJoinAndSelect("communication.type", "communicationType")
.leftJoinAndSelect("communication.member", "member") .leftJoinAndSelect("communication.member", "member")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.where("communication.memberId = :memberId", { memberId: memberId }) .where("communication.memberId = :memberId", { memberId: memberId })
.andWhere("communication.id = :recordId", { recordId: recordId }) .andWhere("communication.id = :recordId", { recordId: recordId })
.getOneOrFail() .getOneOrFail()

View file

@ -39,8 +39,6 @@ export default abstract class MemberService {
) )
.leftJoinAndSelect("membership_first.status", "status_first") .leftJoinAndSelect("membership_first.status", "status_first")
.leftJoinAndSelect("membership_last.status", "status_last") .leftJoinAndSelect("membership_last.status", "status_last")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany( .leftJoinAndMapMany(
"member.preferredCommunication", "member.preferredCommunication",
"member.communications", "member.communications",
@ -48,6 +46,13 @@ export default abstract class MemberService {
"preferredCommunication.preferred = 1" "preferredCommunication.preferred = 1"
) )
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred") .leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndMapOne(
"member.sendNewsletter",
"member.communications",
"sendNewsletter",
"sendNewsletter.isSendNewsletter = 1"
)
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
.leftJoinAndSelect("member.salutation", "salutation"); .leftJoinAndSelect("member.salutation", "salutation");
@ -69,7 +74,7 @@ export default abstract class MemberService {
} }
if (ids.length != 0) { if (ids.length != 0) {
query = query.where("member.id IN (:...ids)", { ids: ids }); query = query.where({ id: ids });
} }
if (!noLimit) { if (!noLimit) {
@ -112,18 +117,22 @@ export default abstract class MemberService {
) )
.leftJoinAndSelect("membership_first.status", "status_first") .leftJoinAndSelect("membership_first.status", "status_first")
.leftJoinAndSelect("membership_last.status", "status_last") .leftJoinAndSelect("membership_last.status", "status_last")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter")
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany( .leftJoinAndMapMany(
"member.preferredCommunication", "member.preferredCommunication",
"member.communications", "member.communications",
"preferredCommunication", "preferredCommunication",
"preferredCommunication.preferred = 1" "preferredCommunication.preferred = 1"
) )
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndMapOne(
"member.sendNewsletter",
"member.communications",
"sendNewsletter",
"sendNewsletter.isSendNewsletter = 1"
)
.leftJoinAndSelect("sendNewsletter.type", "communicationtype")
.leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1") .leftJoinAndMapMany("member.smsAlarming", "member.communications", "smsAlarming", "smsAlarming.isSMSAlarming = 1")
.leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming") .leftJoinAndSelect("smsAlarming.type", "communicationtype_smsAlarming")
.leftJoinAndSelect("preferredCommunication.type", "communicationtype_preferred")
.leftJoinAndSelect("member.salutation", "salutation") .leftJoinAndSelect("member.salutation", "salutation")
.where("member.id = :id", { id: id }) .where("member.id = :id", { id: id })
.getOneOrFail() .getOneOrFail()
@ -185,7 +194,12 @@ export default abstract class MemberService {
return await dataSource return await dataSource
.getRepository(member) .getRepository(member)
.createQueryBuilder("member") .createQueryBuilder("member")
.leftJoinAndSelect("member.sendNewsletter", "sendNewsletter") .leftJoinAndMapOne(
"member.sendNewsletter",
"member.communications",
"sendNewsletter",
"sendNewsletter.isSendNewsletter = 1"
)
.where("member.id = :id", { id: id }) .where("member.id = :id", { id: id })
.getOneOrFail() .getOneOrFail()
.then((res) => { .then((res) => {