@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.
- package/build/actions/akademik/studentReport/syncStudentReport.action.cjs +16 -3
- package/build/actions/akademik/studentReport/syncStudentReport.action.mjs +16 -3
- package/build/actions/tanya/action.reminderQuestions.cjs +68 -36
- package/build/actions/tanya/action.reminderQuestions.mjs +68 -36
- package/package.json +4 -4
|
@@ -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.
|
|
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
|
|
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.
|
|
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
|
|
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-
|
|
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
|
|
28
|
-
|
|
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
|
-
|
|
36
|
-
await
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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-
|
|
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
|
|
26
|
-
|
|
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
|
-
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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":
|
|
87
|
+
"build": 115
|
|
88
88
|
}
|