Skip to content

Commit 8bce119

Browse files
authored
Added email recipient to database (#291)
* added email recipient in database * email report setup * connected fe to db * rm test files * rm test script
1 parent 2af404e commit 8bce119

File tree

14 files changed

+1215
-76
lines changed

14 files changed

+1215
-76
lines changed

backend/crons/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import expireAdminNotes from "./scripts/expireAdminNotes";
33
import expireAdminAnnouncements from "./scripts/expireAdminAnnouncements";
44
import expireLoginStreak from "./scripts/expireLoginStreak";
55
import createAssignedTasks from "./scripts/createAssignedTask";
6+
import { sendWeeklyReports, sendMonthlyReports } from "./scripts/sendReportEmails";
67

78
cron.schedule("0 0 * * * *", async () => {
89
const res = await expireAdminNotes();
@@ -39,3 +40,29 @@ cron.schedule("0 0 * * 1", async () => {
3940
console.log("Could not create assigned tasks");
4041
}
4142
});
43+
44+
// Weekly reports - every Monday at 9 AM
45+
cron.schedule("0 9 * * 1", async () => {
46+
const timestamp = new Date().toISOString();
47+
console.log(`[${timestamp}] Starting weekly report generation and emailing...`);
48+
49+
const res = await sendWeeklyReports();
50+
if (res) {
51+
console.log(`[${timestamp}] Weekly reports sent successfully`);
52+
} else {
53+
console.log(`[${timestamp}] Could not send weekly reports`);
54+
}
55+
});
56+
57+
// Monthly reports - first day of month at 9 AM
58+
cron.schedule("0 9 1 * *", async () => {
59+
const timestamp = new Date().toISOString();
60+
console.log(`[${timestamp}] Starting monthly report generation and emailing...`);
61+
62+
const res = await sendMonthlyReports();
63+
if (res) {
64+
console.log(`[${timestamp}] Monthly reports sent successfully`);
65+
} else {
66+
console.log(`[${timestamp}] Could not send monthly reports`);
67+
}
68+
});
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import prisma from "../../prisma";
2+
3+
interface DataReportParams {
4+
startDate: string; // Format: YYYY-MM-DD
5+
endDate: string; // Format: YYYY-MM-DD
6+
outputPath?: string;
7+
}
8+
9+
interface PraiseWithRaiseTask {
10+
participant_id: number;
11+
task_name: string;
12+
task_status: string;
13+
date: string;
14+
value: number;
15+
comments: string | null;
16+
}
17+
18+
interface ParticipantInfo {
19+
participant_id: number;
20+
account_creation_date: string;
21+
account_removal_date: string | null;
22+
}
23+
24+
interface FinancialInfo {
25+
participant_id: number;
26+
transaction_date: string;
27+
transaction_type: string;
28+
amount: number;
29+
}
30+
31+
interface BadgeInfo {
32+
participant_id: number;
33+
issue_date: string;
34+
badge_earned: string;
35+
level: number;
36+
description: string;
37+
}
38+
39+
interface LoginStats {
40+
participant_id: number;
41+
login_date: string;
42+
}
43+
44+
interface DataReport {
45+
reportPeriod: string;
46+
startDate: string;
47+
endDate: string;
48+
generatedAt: string;
49+
praiseWithRaiseTasks: PraiseWithRaiseTask[];
50+
participantInfo: ParticipantInfo[];
51+
financialInfo: FinancialInfo[];
52+
badges: BadgeInfo[];
53+
loginStats: LoginStats[];
54+
}
55+
56+
// Main function to generate a json for data report
57+
async function generateDataReport(
58+
params: DataReportParams
59+
): Promise<DataReport | null> {
60+
try {
61+
const startDateString = params.startDate;
62+
const endDateString = params.endDate;
63+
64+
// Validate date format and validity
65+
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
66+
if (!dateRegex.test(startDateString) || !dateRegex.test(endDateString)) {
67+
throw new Error("Invalid date format. Use YYYY-MM-DD format.");
68+
}
69+
70+
// Validate that dates are actually valid dates
71+
const startDateObj = new Date(`${startDateString}T00:00:00.000Z`);
72+
const endDateObj = new Date(`${endDateString}T00:00:00.000Z`);
73+
74+
// Check if the date string actually represents the same date when parsed
75+
if (
76+
startDateObj.toISOString().slice(0, 10) !== startDateString ||
77+
endDateObj.toISOString().slice(0, 10) !== endDateString
78+
) {
79+
throw new Error(
80+
"Invalid date values. Please provide valid dates in YYYY-MM-DD format."
81+
);
82+
}
83+
84+
if (
85+
Number.isNaN(startDateObj.getTime()) ||
86+
Number.isNaN(endDateObj.getTime())
87+
) {
88+
throw new Error(
89+
"Invalid date values. Please provide valid dates in YYYY-MM-DD format."
90+
);
91+
}
92+
93+
// Validate that start date is before or equal to end date
94+
if (startDateObj > endDateObj) {
95+
throw new Error("Start date must be before or equal to end date.");
96+
}
97+
98+
console.log(
99+
`Generating report from ${startDateString} to ${endDateString}`
100+
);
101+
102+
// 1. Praise With A Raise Tasks - from AssignedTask table
103+
const praiseWithRaiseTasks = await prisma.assignedTask.findMany({
104+
where: {
105+
start_date: {
106+
gte: startDateString,
107+
lte: endDateString,
108+
},
109+
},
110+
select: {
111+
participant_id: true,
112+
task_name: true,
113+
task_status: true,
114+
start_date: true,
115+
marillac_bucks_addition: true,
116+
comment: true,
117+
},
118+
});
119+
120+
// 2. Participant Information - from Participant table
121+
const participantInfo = await prisma.participant.findMany({
122+
where: {
123+
OR: [
124+
{
125+
account_creation_date: {
126+
gte: startDateString,
127+
lte: endDateString,
128+
},
129+
},
130+
{
131+
account_removal_date: {
132+
gte: startDateString,
133+
lte: endDateString,
134+
},
135+
},
136+
],
137+
},
138+
select: {
139+
participant_id: true,
140+
account_creation_date: true,
141+
account_removal_date: true,
142+
},
143+
});
144+
145+
// 3. Financial Information - from Transaction table
146+
const financialInfo = await prisma.transaction.findMany({
147+
where: {
148+
transaction_date: {
149+
gte: startDateString,
150+
lte: endDateString,
151+
},
152+
},
153+
select: {
154+
participant_id: true,
155+
transaction_date: true,
156+
transaction_type: true,
157+
marillac_bucks: true,
158+
},
159+
});
160+
161+
// 4. Badges - from EarnedBadge table
162+
const badges = await prisma.earnedBadge.findMany({
163+
where: {
164+
date_received: {
165+
gte: startDateString,
166+
lte: endDateString,
167+
},
168+
},
169+
select: {
170+
participant_id: true,
171+
date_received: true,
172+
name: true,
173+
level: true,
174+
description: true,
175+
},
176+
});
177+
178+
// 5. Login Stats - from Login table
179+
const loginStats = await prisma.login.findMany({
180+
where: {
181+
login_date: {
182+
gte: startDateString,
183+
lte: endDateString,
184+
},
185+
},
186+
select: {
187+
participant_id: true,
188+
login_date: true,
189+
},
190+
});
191+
192+
// Format the data
193+
const report: DataReport = {
194+
reportPeriod: `Custom report (${startDateString} to ${endDateString})`,
195+
startDate: startDateString,
196+
endDate: endDateString,
197+
generatedAt: new Date().toISOString(),
198+
praiseWithRaiseTasks: praiseWithRaiseTasks.map((task) => ({
199+
participant_id: task.participant_id,
200+
task_name: task.task_name,
201+
task_status: task.task_status,
202+
date: task.start_date,
203+
value: task.marillac_bucks_addition,
204+
comments: task.comment,
205+
})),
206+
participantInfo: participantInfo.map((participant) => ({
207+
participant_id: participant.participant_id,
208+
account_creation_date: participant.account_creation_date,
209+
account_removal_date: participant.account_removal_date,
210+
})),
211+
financialInfo: financialInfo.map((transaction) => ({
212+
participant_id: transaction.participant_id,
213+
transaction_date: transaction.transaction_date,
214+
transaction_type: transaction.transaction_type,
215+
amount: transaction.marillac_bucks,
216+
})),
217+
badges: badges.map((badge) => ({
218+
participant_id: badge.participant_id,
219+
issue_date: badge.date_received,
220+
badge_earned: badge.name,
221+
level: badge.level,
222+
description: badge.description,
223+
})),
224+
loginStats: loginStats.map((login) => ({
225+
participant_id: login.participant_id,
226+
login_date: login.login_date,
227+
})),
228+
};
229+
230+
// Log summary statistics
231+
console.log(`Report data for (${startDateString} to ${endDateString}):`);
232+
console.log(`- Tasks: ${report.praiseWithRaiseTasks.length}`);
233+
console.log(`- Participant Info Records: ${report.participantInfo.length}`);
234+
console.log(`- Financial Transactions: ${report.financialInfo.length}`);
235+
console.log(`- Badges Earned: ${report.badges.length}`);
236+
console.log(`- Login Records: ${report.loginStats.length}`);
237+
238+
// save to file if outputPath is provided
239+
if (params.outputPath) {
240+
const fs = await import("fs/promises");
241+
await fs.writeFile(params.outputPath, JSON.stringify(report, null, 2));
242+
console.log(`Report saved to: ${params.outputPath}`);
243+
}
244+
245+
return report;
246+
} catch (err) {
247+
console.error(
248+
`Error generating data report (${params.startDate} to ${params.endDate}):`,
249+
err
250+
);
251+
return null;
252+
}
253+
}
254+
255+
// Helper function to calculate date ranges
256+
function calculateDateRange(period: "week" | "month"): {
257+
startDate: string;
258+
endDate: string;
259+
} {
260+
const now = new Date();
261+
const startDate = new Date();
262+
263+
if (period === "week") {
264+
startDate.setDate(now.getDate() - 7);
265+
} else if (period === "month") {
266+
startDate.setMonth(now.getMonth() - 1);
267+
}
268+
269+
return {
270+
startDate: startDate.toLocaleDateString("en-ca"),
271+
endDate: now.toLocaleDateString("en-ca"),
272+
};
273+
}
274+
275+
// Wrapper functions for cron jobs
276+
export async function generateWeeklyReport(): Promise<boolean> {
277+
const { startDate, endDate } = calculateDateRange("week");
278+
const report = await generateDataReport({ startDate, endDate });
279+
return report !== null;
280+
}
281+
282+
export async function generateMonthlyReport(): Promise<boolean> {
283+
const { startDate, endDate } = calculateDateRange("month");
284+
const report = await generateDataReport({ startDate, endDate });
285+
return report !== null;
286+
}
287+
288+
// Export the main function for generating a data report by default
289+
export default generateDataReport;
290+

0 commit comments

Comments
 (0)