enhance: add membership total view
This commit is contained in:
parent
f6a0a61ed8
commit
573f92d098
10 changed files with 219 additions and 3 deletions
|
@ -195,6 +195,19 @@ export async function getMembershipStatisticsById(req: Request, res: Response):
|
||||||
res.json(MembershipFactory.mapToBaseStatistics(member));
|
res.json(MembershipFactory.mapToBaseStatistics(member));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description get member total statistics by id
|
||||||
|
* @param req {Request} Express req object
|
||||||
|
* @param res {Response} Express res object
|
||||||
|
* @returns {Promise<*>}
|
||||||
|
*/
|
||||||
|
export async function getMembershipTotalStatisticsById(req: Request, res: Response): Promise<any> {
|
||||||
|
const memberId = req.params.memberId;
|
||||||
|
let member = await MembershipService.getTotalStatisticsById(memberId);
|
||||||
|
|
||||||
|
res.json(MembershipFactory.mapToSingleTotalStatistic(member));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description get membership by member and record
|
* @description get membership by member and record
|
||||||
* @param req {Request} Express req object
|
* @param req {Request} Express req object
|
||||||
|
|
|
@ -34,7 +34,7 @@ import { query } from "./entity/configuration/query";
|
||||||
import { memberView } from "./views/memberView";
|
import { memberView } from "./views/memberView";
|
||||||
import { memberExecutivePositionsView } from "./views/memberExecutivePositionView";
|
import { memberExecutivePositionsView } from "./views/memberExecutivePositionView";
|
||||||
import { memberQualificationsView } from "./views/memberQualificationsView";
|
import { memberQualificationsView } from "./views/memberQualificationsView";
|
||||||
import { membershipView } from "./views/membershipsView";
|
import { membershipTotalView, membershipView } from "./views/membershipsView";
|
||||||
import { template } from "./entity/configuration/template";
|
import { template } from "./entity/configuration/template";
|
||||||
import { templateUsage } from "./entity/configuration/templateUsage";
|
import { templateUsage } from "./entity/configuration/templateUsage";
|
||||||
import { newsletter } from "./entity/club/newsletter/newsletter";
|
import { newsletter } from "./entity/club/newsletter/newsletter";
|
||||||
|
@ -57,6 +57,7 @@ import { MemberCreatedAt1746006549262 } from "./migrations/1746006549262-memberC
|
||||||
import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLoginRoutine";
|
import { UserLoginRoutine1746252454922 } from "./migrations/1746252454922-UserLoginRoutine";
|
||||||
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";
|
||||||
|
|
||||||
configCheck();
|
configCheck();
|
||||||
|
|
||||||
|
@ -109,6 +110,7 @@ const dataSource = new DataSource({
|
||||||
memberExecutivePositionsView,
|
memberExecutivePositionsView,
|
||||||
memberQualificationsView,
|
memberQualificationsView,
|
||||||
membershipView,
|
membershipView,
|
||||||
|
membershipTotalView,
|
||||||
webapi,
|
webapi,
|
||||||
webapiPermission,
|
webapiPermission,
|
||||||
setting,
|
setting,
|
||||||
|
@ -125,6 +127,7 @@ const dataSource = new DataSource({
|
||||||
MemberCreatedAt1746006549262,
|
MemberCreatedAt1746006549262,
|
||||||
UserLoginRoutine1746252454922,
|
UserLoginRoutine1746252454922,
|
||||||
AddDateToNewsletter1748509435932,
|
AddDateToNewsletter1748509435932,
|
||||||
|
AddMembershipTotalView1748607842929,
|
||||||
],
|
],
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
migrationsTransactionMode: "each",
|
migrationsTransactionMode: "each",
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { membership } from "../../../../entity/club/member/membership";
|
import { membership } from "../../../../entity/club/member/membership";
|
||||||
import {
|
import {
|
||||||
MembershipStatisticsViewModel,
|
MembershipStatisticsViewModel,
|
||||||
|
MembershipTotalStatisticsViewModel,
|
||||||
MembershipViewModel,
|
MembershipViewModel,
|
||||||
} from "../../../../viewmodel/admin/club/member/membership.models";
|
} from "../../../../viewmodel/admin/club/member/membership.models";
|
||||||
import { membershipView } from "../../../../views/membershipsView";
|
import { membershipTotalView, membershipView } from "../../../../views/membershipsView";
|
||||||
import DateMappingHelper from "./dateMappingHelper";
|
import DateMappingHelper from "./dateMappingHelper";
|
||||||
|
|
||||||
export default abstract class MembershipFactory {
|
export default abstract class MembershipFactory {
|
||||||
|
@ -53,6 +54,25 @@ export default abstract class MembershipFactory {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description map view record to MembershipTotalStatisticsViewModel
|
||||||
|
* @param {membershipTotalView} record
|
||||||
|
* @returns {MembershipTotalStatisticsViewModel}
|
||||||
|
*/
|
||||||
|
public static mapToSingleTotalStatistic(record: membershipTotalView): MembershipTotalStatisticsViewModel {
|
||||||
|
return {
|
||||||
|
durationInDays: record.durationInDays,
|
||||||
|
durationInYears: record.durationInYears,
|
||||||
|
exactDuration: DateMappingHelper.mapDate(record.exactDuration),
|
||||||
|
memberId: record.memberId,
|
||||||
|
memberSalutation: record.memberSalutation,
|
||||||
|
memberFirstname: record.memberFirstname,
|
||||||
|
memberLastname: record.memberLastname,
|
||||||
|
memberNameaffix: record.memberNameaffix,
|
||||||
|
memberBirthdate: record.memberBirthdate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description map records to MembershipStatisticsViewModel
|
* @description map records to MembershipStatisticsViewModel
|
||||||
* @param {Array<membershipView>} records
|
* @param {Array<membershipView>} records
|
||||||
|
|
|
@ -22,6 +22,7 @@ export default abstract class DynamicQueryBuilder {
|
||||||
"memberExecutivePositionsView",
|
"memberExecutivePositionsView",
|
||||||
"memberQualificationsView",
|
"memberQualificationsView",
|
||||||
"membershipView",
|
"membershipView",
|
||||||
|
"membershipTotalView",
|
||||||
];
|
];
|
||||||
|
|
||||||
public static getTableMeta(tableName: string): TableMeta {
|
public static getTableMeta(tableName: string): TableMeta {
|
||||||
|
|
21
src/migrations/1748607842929-addMembershipTotalView.ts
Normal file
21
src/migrations/1748607842929-addMembershipTotalView.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
import { DB_TYPE } from "../env.defaults";
|
||||||
|
import {
|
||||||
|
membership_total_view_mysql,
|
||||||
|
membership_total_view_postgres,
|
||||||
|
membership_total_view_sqlite,
|
||||||
|
} from "./baseSchemaTables/member";
|
||||||
|
|
||||||
|
export class AddMembershipTotalView1748607842929 implements MigrationInterface {
|
||||||
|
name = "AddMembershipTotalView1748607842929";
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
if (DB_TYPE == "postgres") await queryRunner.createView(membership_total_view_postgres, true);
|
||||||
|
else if (DB_TYPE == "mysql") await queryRunner.createView(membership_total_view_mysql, true);
|
||||||
|
else if (DB_TYPE == "sqlite") await queryRunner.createView(membership_total_view_sqlite, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.dropView("membership_total_view");
|
||||||
|
}
|
||||||
|
}
|
|
@ -529,3 +529,76 @@ export const membership_view_sqlite = new View({
|
||||||
GROUP BY status.id, member.id, salutation.id
|
GROUP BY status.id, member.id, salutation.id
|
||||||
`,
|
`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const membership_total_view_mysql = new View({
|
||||||
|
name: "membership_total_view",
|
||||||
|
expression: `
|
||||||
|
SELECT
|
||||||
|
\`member\`.\`id\` AS \`memberId\`,
|
||||||
|
\`member\`.\`firstname\` AS \`memberFirstname\`,
|
||||||
|
\`member\`.\`lastname\` AS \`memberLastname\`,
|
||||||
|
\`member\`.\`nameaffix\` AS \`memberNameaffix\`,
|
||||||
|
\`member\`.\`birthdate\` AS \`memberBirthdate\`,
|
||||||
|
\`salutation\`.\`salutation\` AS \`memberSalutation\`,
|
||||||
|
SUM(DATEDIFF(COALESCE(\`membership\`.\`end\`, CURDATE()), \`membership\`.\`start\`)) AS \`durationInDays\`,
|
||||||
|
SUM(TIMESTAMPDIFF(YEAR, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE()))) AS \`durationInYears\`,
|
||||||
|
CONCAT(
|
||||||
|
SUM(FLOOR(TIMESTAMPDIFF(DAY, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())) / 365.25)),
|
||||||
|
' years ',
|
||||||
|
SUM(FLOOR(MOD(TIMESTAMPDIFF(MONTH, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())), 12))),
|
||||||
|
' months ',
|
||||||
|
SUM(FLOOR(MOD(TIMESTAMPDIFF(DAY, \`membership\`.\`start\`, COALESCE(\`membership\`.\`end\`, CURDATE())), 30))),
|
||||||
|
' days'
|
||||||
|
) AS \`exactDuration\`
|
||||||
|
FROM \`membership\` \`membership\`
|
||||||
|
LEFT JOIN \`membership_status\` \`status\` ON \`status\`.\`id\`=\`membership\`.\`statusId\`
|
||||||
|
LEFT JOIN \`member\` \`member\` ON \`member\`.\`id\`=\`membership\`.\`memberId\`
|
||||||
|
LEFT JOIN \`salutation\` \`salutation\` ON \`salutation\`.\`id\`=\`member\`.\`salutationId\`
|
||||||
|
GROUP BY \`member\`.\`id\`, \`salutation\`.\`id\`
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const membership_total_view_postgres = new View({
|
||||||
|
name: "membership_total_view",
|
||||||
|
expression: `
|
||||||
|
SELECT
|
||||||
|
"member"."id" AS "memberId",
|
||||||
|
"member"."firstname" AS "memberFirstname",
|
||||||
|
"member"."lastname" AS "memberLastname",
|
||||||
|
"member"."nameaffix" AS "memberNameaffix",
|
||||||
|
"member"."birthdate" AS "memberBirthdate",
|
||||||
|
"salutation"."salutation" AS "memberSalutation",
|
||||||
|
SUM(COALESCE("membership"."end", CURRENT_DATE) - "membership"."start") AS "durationInDays",
|
||||||
|
SUM(EXTRACT(YEAR FROM AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start"))) AS "durationInYears",
|
||||||
|
SUM(AGE(COALESCE("membership"."end", CURRENT_DATE), "membership"."start")) AS "exactDuration"
|
||||||
|
FROM "membership" "membership"
|
||||||
|
LEFT JOIN "membership_status" "status" ON "status"."id"="membership"."statusId"
|
||||||
|
LEFT JOIN "member" "member" ON "member"."id"="membership"."memberId"
|
||||||
|
LEFT JOIN "salutation" "salutation" ON "salutation"."id"="member"."salutationId"
|
||||||
|
GROUP BY "member"."id", "salutation"."id"
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const membership_total_view_sqlite = new View({
|
||||||
|
name: "membership_total_view",
|
||||||
|
expression: `
|
||||||
|
SELECT
|
||||||
|
member.id AS memberId,
|
||||||
|
member.firstname AS memberFirstname,
|
||||||
|
member.lastname AS memberLastname,
|
||||||
|
member.nameaffix AS memberNameaffix,
|
||||||
|
member.birthdate AS memberBirthdate,
|
||||||
|
salutation.salutation AS memberSalutation,
|
||||||
|
SUM(JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) AS durationInDays,
|
||||||
|
SUM(FLOOR((JULIANDAY(COALESCE(membership.end, DATE('now'))) - JULIANDAY(membership.start)) / 365.25)) AS durationInYears,
|
||||||
|
SUM((strftime('%Y', COALESCE(membership.end, DATE('now'))) - strftime('%Y', membership.start))) || ' years ' ||
|
||||||
|
SUM((strftime('%m', COALESCE(membership.end, DATE('now'))) - strftime('%m', membership.start))) || ' months ' ||
|
||||||
|
SUM((strftime('%d', COALESCE(membership.end, DATE('now'))) - strftime('%d', membership.start))) || ' days'
|
||||||
|
AS exactDuration
|
||||||
|
FROM membership membership
|
||||||
|
LEFT JOIN membership_status status ON status.id=membership.statusId
|
||||||
|
LEFT JOIN member member ON member.id=membership.memberId
|
||||||
|
LEFT JOIN salutation salutation ON salutation.id=member.salutationId
|
||||||
|
GROUP BY member.id, salutation.id
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
getMembershipByMemberAndRecord,
|
getMembershipByMemberAndRecord,
|
||||||
getMembershipsByMember,
|
getMembershipsByMember,
|
||||||
getMembershipStatisticsById,
|
getMembershipStatisticsById,
|
||||||
|
getMembershipTotalStatisticsById,
|
||||||
getMemberStatisticsById,
|
getMemberStatisticsById,
|
||||||
getQualificationByMemberAndRecord,
|
getQualificationByMemberAndRecord,
|
||||||
getQualificationsByMember,
|
getQualificationsByMember,
|
||||||
|
@ -72,6 +73,10 @@ router.get("/:memberId/memberships/statistics", async (req: Request, res: Respon
|
||||||
await getMembershipStatisticsById(req, res);
|
await getMembershipStatisticsById(req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get("/:memberId/memberships/totalstatistics", async (req: Request, res: Response) => {
|
||||||
|
await getMembershipTotalStatisticsById(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
router.get("/:memberId/membership/:id", async (req: Request, res: Response) => {
|
router.get("/:memberId/membership/:id", async (req: Request, res: Response) => {
|
||||||
await getMembershipByMemberAndRecord(req, res);
|
await getMembershipByMemberAndRecord(req, res);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { dataSource } from "../../../data-source";
|
||||||
import { membership } from "../../../entity/club/member/membership";
|
import { membership } from "../../../entity/club/member/membership";
|
||||||
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
import DatabaseActionException from "../../../exceptions/databaseActionException";
|
||||||
import InternalException from "../../../exceptions/internalException";
|
import InternalException from "../../../exceptions/internalException";
|
||||||
import { membershipView } from "../../../views/membershipsView";
|
import { membershipTotalView, membershipView } from "../../../views/membershipsView";
|
||||||
|
|
||||||
export default abstract class MembershipService {
|
export default abstract class MembershipService {
|
||||||
/**
|
/**
|
||||||
|
@ -66,4 +66,23 @@ export default abstract class MembershipService {
|
||||||
throw new DatabaseActionException("SELECT", "membershipView", err);
|
throw new DatabaseActionException("SELECT", "membershipView", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description get membership total statistics by memberId
|
||||||
|
* @param {string} memberId
|
||||||
|
* @returns {Promise<Array<membershipTotalView>>}
|
||||||
|
*/
|
||||||
|
static async getTotalStatisticsById(memberId: string): Promise<membershipTotalView> {
|
||||||
|
return await dataSource
|
||||||
|
.getRepository(membershipTotalView)
|
||||||
|
.createQueryBuilder("membershipTotalView")
|
||||||
|
.where("membershipTotalView.memberId = :memberId", { memberId: memberId })
|
||||||
|
.getOneOrFail()
|
||||||
|
.then((res) => {
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw new DatabaseActionException("SELECT", "membershipTotalView", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,3 +20,15 @@ export interface MembershipStatisticsViewModel {
|
||||||
memberNameaffix: string;
|
memberNameaffix: string;
|
||||||
memberBirthdate: Date;
|
memberBirthdate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MembershipTotalStatisticsViewModel {
|
||||||
|
durationInDays: number;
|
||||||
|
durationInYears: number;
|
||||||
|
exactDuration: string;
|
||||||
|
memberId: string;
|
||||||
|
memberSalutation: string;
|
||||||
|
memberFirstname: string;
|
||||||
|
memberLastname: string;
|
||||||
|
memberNameaffix: string;
|
||||||
|
memberBirthdate: Date;
|
||||||
|
}
|
||||||
|
|
|
@ -89,3 +89,52 @@ export class membershipView {
|
||||||
@ViewColumn()
|
@ViewColumn()
|
||||||
memberBirthdate: Date;
|
memberBirthdate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewEntity({
|
||||||
|
expression: (datasource: DataSource) =>
|
||||||
|
datasource
|
||||||
|
.getRepository(membership)
|
||||||
|
.createQueryBuilder("membership")
|
||||||
|
.select("member.id", "memberId")
|
||||||
|
.addSelect("member.firstname", "memberFirstname")
|
||||||
|
.addSelect("member.lastname", "memberLastname")
|
||||||
|
.addSelect("member.nameaffix", "memberNameaffix")
|
||||||
|
.addSelect("member.birthdate", "memberBirthdate")
|
||||||
|
.addSelect("salutation.salutation", "memberSalutation")
|
||||||
|
.addSelect(durationInDays, "durationInDays")
|
||||||
|
.addSelect(durationInYears, "durationInYears")
|
||||||
|
.addSelect(exactDuration, "exactDuration")
|
||||||
|
.leftJoin("membership.status", "status")
|
||||||
|
.leftJoin("membership.member", "member")
|
||||||
|
.leftJoin("member.salutation", "salutation")
|
||||||
|
.groupBy("status.id")
|
||||||
|
.addGroupBy("member.id"),
|
||||||
|
})
|
||||||
|
export class membershipTotalView {
|
||||||
|
@ViewColumn()
|
||||||
|
durationInDays: number;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
durationInYears: number;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
exactDuration: string;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
memberId: string;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
memberSalutation: string;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
memberFirstname: string;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
memberLastname: string;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
memberNameaffix: string;
|
||||||
|
|
||||||
|
@ViewColumn()
|
||||||
|
memberBirthdate: Date;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue