@neutron.co.id/pendidikan-operation 1.26.25-beta.1 → 1.26.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -239,7 +239,6 @@ const syncStudentReport = operation.Action.define({
239
239
  }
240
240
  })
241
241
  }, stream);
242
- console.log("sessionHybrid", scheduleCount);
243
242
  const totalHybrid = sessionHybrid.payload?.data?.total || 0;
244
243
  if (sessionCount > 0) {
245
244
  await dbStudent.updateOne(
@@ -268,7 +267,7 @@ const syncStudentReport = operation.Action.define({
268
267
  }
269
268
  );
270
269
  }
271
- const activityResult = await stream.actions.data.count.execute({
270
+ const activityResult = await stream.actions.data.getMany.execute({
272
271
  model: "neu:jadwal:classActivity",
273
272
  query: query.Query.define({
274
273
  filter: {
@@ -277,10 +276,24 @@ const syncStudentReport = operation.Action.define({
277
276
  $gte: input.startedAt,
278
277
  $lte: input.endedAt
279
278
  }
279
+ },
280
+ fields: {
281
+ id: 1,
282
+ studentId: 1,
283
+ classSession: {
284
+ id: 1,
285
+ title: 1,
286
+ type: 1,
287
+ branchIds: 1
288
+ }
280
289
  }
281
290
  })
282
291
  }, stream);
283
- const totalActivities = activityResult.payload?.data?.total || 0;
292
+ const totalActivities = activityResult.payload.data?.filter((item) => {
293
+ const isOnline = item.classSession?.type === "online";
294
+ const hasMatchingBranch = input.branchIds?.some((branchId) => item.classSession?.branchIds?.includes(branchId));
295
+ return isOnline && hasMatchingBranch;
296
+ }).length || 0;
284
297
  if (sessionCount > 0) {
285
298
  await dbStudent.updateOne(
286
299
  { _id: input.studentId },
@@ -237,7 +237,6 @@ const syncStudentReport = Action.define({
237
237
  }
238
238
  })
239
239
  }, stream);
240
- console.log("sessionHybrid", scheduleCount);
241
240
  const totalHybrid = sessionHybrid.payload?.data?.total || 0;
242
241
  if (sessionCount > 0) {
243
242
  await dbStudent.updateOne(
@@ -266,7 +265,7 @@ const syncStudentReport = Action.define({
266
265
  }
267
266
  );
268
267
  }
269
- const activityResult = await stream.actions.data.count.execute({
268
+ const activityResult = await stream.actions.data.getMany.execute({
270
269
  model: "neu:jadwal:classActivity",
271
270
  query: Query.define({
272
271
  filter: {
@@ -275,10 +274,24 @@ const syncStudentReport = Action.define({
275
274
  $gte: input.startedAt,
276
275
  $lte: input.endedAt
277
276
  }
277
+ },
278
+ fields: {
279
+ id: 1,
280
+ studentId: 1,
281
+ classSession: {
282
+ id: 1,
283
+ title: 1,
284
+ type: 1,
285
+ branchIds: 1
286
+ }
278
287
  }
279
288
  })
280
289
  }, stream);
281
- const totalActivities = activityResult.payload?.data?.total || 0;
290
+ const totalActivities = activityResult.payload.data?.filter((item) => {
291
+ const isOnline = item.classSession?.type === "online";
292
+ const hasMatchingBranch = input.branchIds?.some((branchId) => item.classSession?.branchIds?.includes(branchId));
293
+ return isOnline && hasMatchingBranch;
294
+ }).length || 0;
282
295
  if (sessionCount > 0) {
283
296
  await dbStudent.updateOne(
284
297
  { _id: input.studentId },
@@ -6,16 +6,28 @@ const utils = require('@neon.id/utils');
6
6
  async function reminderQuestions(dbs, config) {
7
7
  const dbQuestion = dbs["neu-tanya"].models["neu:tanya:question"];
8
8
  const dbIdentitas = dbs["neo-identitas"].models["neo:identitas:identifier"];
9
+ const dbUser = dbs["neo1-identitas"].models["neo:identitas:user"];
9
10
  const dbTeacher = dbs["neu-akademik"].models["neu:akademik:teacher"];
10
11
  const dbSubject = dbs["neu-akademik"].models["neu:akademik:academicSubject"];
11
12
  const dbStudent = dbs["neu-akademik"].models["neu:akademik:student"];
12
13
  const waitingQuestions = await dbQuestion.find({
13
14
  status: "waiting",
14
15
  createdAt: {
15
- $gte: /* @__PURE__ */ new Date("2025-06-12T00:00:00.000Z"),
16
+ $gte: /* @__PURE__ */ new Date("2025-06-11T00:00:00.000Z"),
16
17
  $lte: /* @__PURE__ */ new Date()
17
18
  }
18
19
  });
20
+ function formatTimeAgo(from, to) {
21
+ const diffMinutes = utils.DateUtil.diff(from, to, "minutes") ?? 0;
22
+ if (diffMinutes < 60)
23
+ return `${diffMinutes} menit`;
24
+ const diffHours = utils.DateUtil.diff(from, to, "hours") ?? 0;
25
+ if (diffHours < 24)
26
+ return `${diffHours} jam`;
27
+ const diffDays = utils.DateUtil.diff(from, to, "days");
28
+ return `${diffDays} hari`;
29
+ }
30
+ const questionMap = /* @__PURE__ */ new Map();
19
31
  for (const q of waitingQuestions) {
20
32
  const subject = await dbSubject.findOne({ _id: q.subjectId });
21
33
  const teachers = await dbTeacher.find({
@@ -24,44 +36,64 @@ async function reminderQuestions(dbs, config) {
24
36
  });
25
37
  const student = await dbStudent.findOne({ _id: q.studentId });
26
38
  for (const teacher of teachers) {
27
- const identifier = await dbIdentitas.findOne({ userId: teacher.userId });
28
- const email = identifier?.email || teacher.user?.email;
29
- console.log("\u{1F680} ~ reminderQuestions ~ identifier:", identifier);
30
- console.log(`[CRON] Question ${q.code} \u2192 ${teachers.length} teachers found`);
31
- if (!email) {
32
- console.warn(`[CRON] Skipping teacher ${teacher._id} karena tidak ada email dari userId: ${teacher.userId}`);
39
+ const userIdStr = teacher.userId?.toString();
40
+ if (!userIdStr)
33
41
  continue;
34
- }
35
- try {
36
- await ofetch.ofetch(`${config.novuApi.url}/v1/events/trigger`, {
37
- method: "POST",
38
- headers: {
39
- "Authorization": `ApiKey ${config.novuApi.token}`,
40
- "Content-Type": "application/json"
41
- },
42
- body: JSON.stringify({
43
- name: "question-respond-reminder",
44
- to: {
45
- subscriberId: teacher.userId || identifier?.userId,
46
- email,
47
- firstName: teacher.user?.name || identifier?.profile?.given_name,
48
- lastName: ""
49
- },
50
- payload: {
51
- questionId: q.id,
52
- totalQuestions: waitingQuestions.length,
53
- code: q.code,
54
- studentName: student?.name,
55
- subject: subject?.name,
56
- date: utils.DateUtil.formatTz(q.askedAt, { pattern: "yyyy-MM-dd HH:mm" }),
57
- studentTimeAgo: utils.DateUtil.diff(q.askedAt, /* @__PURE__ */ new Date(), "hours"),
58
- url: `${config.appFrotendUrl}/tanya/teachers`
59
- }
60
- })
42
+ if (!questionMap.has(userIdStr)) {
43
+ const identifier = await dbIdentitas.findOne({ userId: teacher.userId });
44
+ const user = await dbUser.findOne({ _id: teacher.userId });
45
+ questionMap.set(userIdStr, {
46
+ teacher,
47
+ identifier,
48
+ user,
49
+ questions: []
61
50
  });
62
- } catch (error) {
63
- console.error(error);
64
51
  }
52
+ const entry = questionMap.get(userIdStr);
53
+ entry?.questions.push({
54
+ q,
55
+ subject,
56
+ student
57
+ });
58
+ }
59
+ }
60
+ for (const [_userId, { teacher, identifier, user, questions }] of questionMap.entries()) {
61
+ const email = identifier?.provider === "email" && identifier?.value || identifier?.email || identifier?.profile?.email || user?.email;
62
+ if (!email) {
63
+ console.warn(`[CRON] Skipping teacher ${teacher.name} karena tidak ada email`);
64
+ continue;
65
+ }
66
+ const firstQuestion = questions[0];
67
+ try {
68
+ await ofetch.ofetch(`${config.novuApi.url}/v1/events/trigger`, {
69
+ method: "POST",
70
+ headers: {
71
+ "Authorization": `ApiKey ${config.novuApi.token}`,
72
+ "Content-Type": "application/json"
73
+ },
74
+ body: JSON.stringify({
75
+ name: "question-respond-reminder",
76
+ to: {
77
+ subscriberId: teacher.userId || identifier?.userId,
78
+ email,
79
+ firstName: teacher.user?.name || identifier?.profile?.given_name,
80
+ lastName: ""
81
+ },
82
+ payload: {
83
+ questionId: firstQuestion.q.id,
84
+ totalQuestions: questions.length,
85
+ code: firstQuestion.q.code,
86
+ studentName: firstQuestion.student?.name,
87
+ subject: firstQuestion.subject?.name,
88
+ date: utils.DateUtil.formatTz(firstQuestion.q.askedAt, { pattern: "yyyy-MM-dd HH:mm" }),
89
+ timeAgo: formatTimeAgo(firstQuestion.q.askedAt, /* @__PURE__ */ new Date()),
90
+ url: `${config.appFrontendUrl}/tanya/teachers`
91
+ }
92
+ })
93
+ });
94
+ console.log(`[CRON] \u2705 Reminder dikirim ke ${email} untuk ${questions.length} pertanyaan`);
95
+ } catch (error) {
96
+ console.error(error);
65
97
  }
66
98
  }
67
99
  }
@@ -4,16 +4,28 @@ import { DateUtil } from '@neon.id/utils';
4
4
  async function reminderQuestions(dbs, config) {
5
5
  const dbQuestion = dbs["neu-tanya"].models["neu:tanya:question"];
6
6
  const dbIdentitas = dbs["neo-identitas"].models["neo:identitas:identifier"];
7
+ const dbUser = dbs["neo1-identitas"].models["neo:identitas:user"];
7
8
  const dbTeacher = dbs["neu-akademik"].models["neu:akademik:teacher"];
8
9
  const dbSubject = dbs["neu-akademik"].models["neu:akademik:academicSubject"];
9
10
  const dbStudent = dbs["neu-akademik"].models["neu:akademik:student"];
10
11
  const waitingQuestions = await dbQuestion.find({
11
12
  status: "waiting",
12
13
  createdAt: {
13
- $gte: /* @__PURE__ */ new Date("2025-06-12T00:00:00.000Z"),
14
+ $gte: /* @__PURE__ */ new Date("2025-06-11T00:00:00.000Z"),
14
15
  $lte: /* @__PURE__ */ new Date()
15
16
  }
16
17
  });
18
+ function formatTimeAgo(from, to) {
19
+ const diffMinutes = DateUtil.diff(from, to, "minutes") ?? 0;
20
+ if (diffMinutes < 60)
21
+ return `${diffMinutes} menit`;
22
+ const diffHours = DateUtil.diff(from, to, "hours") ?? 0;
23
+ if (diffHours < 24)
24
+ return `${diffHours} jam`;
25
+ const diffDays = DateUtil.diff(from, to, "days");
26
+ return `${diffDays} hari`;
27
+ }
28
+ const questionMap = /* @__PURE__ */ new Map();
17
29
  for (const q of waitingQuestions) {
18
30
  const subject = await dbSubject.findOne({ _id: q.subjectId });
19
31
  const teachers = await dbTeacher.find({
@@ -22,44 +34,64 @@ async function reminderQuestions(dbs, config) {
22
34
  });
23
35
  const student = await dbStudent.findOne({ _id: q.studentId });
24
36
  for (const teacher of teachers) {
25
- const identifier = await dbIdentitas.findOne({ userId: teacher.userId });
26
- const email = identifier?.email || teacher.user?.email;
27
- console.log("\u{1F680} ~ reminderQuestions ~ identifier:", identifier);
28
- console.log(`[CRON] Question ${q.code} \u2192 ${teachers.length} teachers found`);
29
- if (!email) {
30
- console.warn(`[CRON] Skipping teacher ${teacher._id} karena tidak ada email dari userId: ${teacher.userId}`);
37
+ const userIdStr = teacher.userId?.toString();
38
+ if (!userIdStr)
31
39
  continue;
32
- }
33
- try {
34
- await ofetch(`${config.novuApi.url}/v1/events/trigger`, {
35
- method: "POST",
36
- headers: {
37
- "Authorization": `ApiKey ${config.novuApi.token}`,
38
- "Content-Type": "application/json"
39
- },
40
- body: JSON.stringify({
41
- name: "question-respond-reminder",
42
- to: {
43
- subscriberId: teacher.userId || identifier?.userId,
44
- email,
45
- firstName: teacher.user?.name || identifier?.profile?.given_name,
46
- lastName: ""
47
- },
48
- payload: {
49
- questionId: q.id,
50
- totalQuestions: waitingQuestions.length,
51
- code: q.code,
52
- studentName: student?.name,
53
- subject: subject?.name,
54
- date: DateUtil.formatTz(q.askedAt, { pattern: "yyyy-MM-dd HH:mm" }),
55
- studentTimeAgo: DateUtil.diff(q.askedAt, /* @__PURE__ */ new Date(), "hours"),
56
- url: `${config.appFrotendUrl}/tanya/teachers`
57
- }
58
- })
40
+ if (!questionMap.has(userIdStr)) {
41
+ const identifier = await dbIdentitas.findOne({ userId: teacher.userId });
42
+ const user = await dbUser.findOne({ _id: teacher.userId });
43
+ questionMap.set(userIdStr, {
44
+ teacher,
45
+ identifier,
46
+ user,
47
+ questions: []
59
48
  });
60
- } catch (error) {
61
- console.error(error);
62
49
  }
50
+ const entry = questionMap.get(userIdStr);
51
+ entry?.questions.push({
52
+ q,
53
+ subject,
54
+ student
55
+ });
56
+ }
57
+ }
58
+ for (const [_userId, { teacher, identifier, user, questions }] of questionMap.entries()) {
59
+ const email = identifier?.provider === "email" && identifier?.value || identifier?.email || identifier?.profile?.email || user?.email;
60
+ if (!email) {
61
+ console.warn(`[CRON] Skipping teacher ${teacher.name} karena tidak ada email`);
62
+ continue;
63
+ }
64
+ const firstQuestion = questions[0];
65
+ try {
66
+ await ofetch(`${config.novuApi.url}/v1/events/trigger`, {
67
+ method: "POST",
68
+ headers: {
69
+ "Authorization": `ApiKey ${config.novuApi.token}`,
70
+ "Content-Type": "application/json"
71
+ },
72
+ body: JSON.stringify({
73
+ name: "question-respond-reminder",
74
+ to: {
75
+ subscriberId: teacher.userId || identifier?.userId,
76
+ email,
77
+ firstName: teacher.user?.name || identifier?.profile?.given_name,
78
+ lastName: ""
79
+ },
80
+ payload: {
81
+ questionId: firstQuestion.q.id,
82
+ totalQuestions: questions.length,
83
+ code: firstQuestion.q.code,
84
+ studentName: firstQuestion.student?.name,
85
+ subject: firstQuestion.subject?.name,
86
+ date: DateUtil.formatTz(firstQuestion.q.askedAt, { pattern: "yyyy-MM-dd HH:mm" }),
87
+ timeAgo: formatTimeAgo(firstQuestion.q.askedAt, /* @__PURE__ */ new Date()),
88
+ url: `${config.appFrontendUrl}/tanya/teachers`
89
+ }
90
+ })
91
+ });
92
+ console.log(`[CRON] \u2705 Reminder dikirim ke ${email} untuk ${questions.length} pertanyaan`);
93
+ } catch (error) {
94
+ console.error(error);
63
95
  }
64
96
  }
65
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neutron.co.id/pendidikan-operation",
3
- "version": "1.26.25-beta.1",
3
+ "version": "1.26.26",
4
4
  "description": "Operation package of Neutron Pendidikan.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "contributors": [
@@ -41,7 +41,7 @@
41
41
  "@neon.id/z": "^1.16.0",
42
42
  "@neutron.co.id/akademik-models": "^1.19.16-beta.1",
43
43
  "@neutron.co.id/jadwal-models": "^1.19.11-beta.1",
44
- "@neutron.co.id/pendidikan-types": "^1.22.18-beta.1",
44
+ "@neutron.co.id/pendidikan-types": "^1.22.20",
45
45
  "@neutron.co.id/penilaian-models": "^1.17.8",
46
46
  "@neutron.co.id/personalia-models": "^1.11.6",
47
47
  "@neutron.co.id/tanya-models": "^1.13.4-beta.1",
@@ -76,7 +76,7 @@
76
76
  "@neon.id/z": "^1.16.0",
77
77
  "@neutron.co.id/akademik-models": "^1.19.16-beta.1",
78
78
  "@neutron.co.id/jadwal-models": "^1.19.11-beta.1",
79
- "@neutron.co.id/pendidikan-types": "^1.22.18-beta.1",
79
+ "@neutron.co.id/pendidikan-types": "^1.22.20",
80
80
  "@neutron.co.id/penilaian-models": "^1.17.8",
81
81
  "@neutron.co.id/personalia-models": "^1.11.6",
82
82
  "@neutron.co.id/tanya-models": "^1.13.4-beta.1"
@@ -84,5 +84,5 @@
84
84
  "publishConfig": {
85
85
  "access": "public"
86
86
  },
87
- "build": 113
87
+ "build": 115
88
88
  }