@studious-lms/server 1.23.0 → 1.25.0
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/.env.example +0 -1
- package/data/curriculum/nepal-cdc-g4-g5/README.md +90 -0
- package/data/curriculum/nepal-cdc-g4-g5/manifest.json +97 -0
- package/data/curriculum/nepal-cdc-g4-g5/proficiency-scale.json +76 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/english.json +6936 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/health-physical-creative-arts.json +3401 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/mathematics.json +1511 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/nepali.json +7928 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/science-technology.json +2420 -0
- package/data/curriculum/nepal-cdc-g4-g5/subjects/social-studies.json +2177 -0
- package/dist/lib/assignmentTypeDefaults.d.ts +41 -0
- package/dist/lib/assignmentTypeDefaults.d.ts.map +1 -0
- package/dist/lib/assignmentTypeDefaults.js +167 -0
- package/dist/lib/assignmentTypeDefaults.js.map +1 -0
- package/dist/lib/config/env.d.ts +0 -8
- package/dist/lib/config/env.d.ts.map +1 -1
- package/dist/lib/config/env.js +2 -5
- package/dist/lib/config/env.js.map +1 -1
- package/dist/lib/feePdf.d.ts +19 -0
- package/dist/lib/feePdf.d.ts.map +1 -0
- package/dist/lib/feePdf.js +167 -0
- package/dist/lib/feePdf.js.map +1 -0
- package/dist/lib/feePdfFormat.d.ts +4 -0
- package/dist/lib/feePdfFormat.d.ts.map +1 -0
- package/dist/lib/feePdfFormat.js +16 -0
- package/dist/lib/feePdfFormat.js.map +1 -0
- package/dist/lib/idCardPdf.d.ts +26 -0
- package/dist/lib/idCardPdf.d.ts.map +1 -0
- package/dist/lib/idCardPdf.js +272 -0
- package/dist/lib/idCardPdf.js.map +1 -0
- package/dist/lib/pdfLetterhead.d.ts +41 -0
- package/dist/lib/pdfLetterhead.d.ts.map +1 -0
- package/dist/lib/pdfLetterhead.js +86 -0
- package/dist/lib/pdfLetterhead.js.map +1 -0
- package/dist/lib/transcriptPdf.d.ts +55 -0
- package/dist/lib/transcriptPdf.d.ts.map +1 -0
- package/dist/lib/transcriptPdf.js +303 -0
- package/dist/lib/transcriptPdf.js.map +1 -0
- package/dist/models/academicCalendar.d.ts +198 -0
- package/dist/models/academicCalendar.d.ts.map +1 -0
- package/dist/models/academicCalendar.js +106 -0
- package/dist/models/academicCalendar.js.map +1 -0
- package/dist/models/agenda.d.ts +8 -0
- package/dist/models/agenda.d.ts.map +1 -1
- package/dist/models/announcement.d.ts +2 -0
- package/dist/models/announcement.d.ts.map +1 -1
- package/dist/models/assignment.d.ts +66 -0
- package/dist/models/assignment.d.ts.map +1 -1
- package/dist/models/attendance.d.ts +659 -21
- package/dist/models/attendance.d.ts.map +1 -1
- package/dist/models/attendance.js +547 -75
- package/dist/models/attendance.js.map +1 -1
- package/dist/models/auth.d.ts +2 -0
- package/dist/models/auth.d.ts.map +1 -1
- package/dist/models/class.d.ts +12 -0
- package/dist/models/class.d.ts.map +1 -1
- package/dist/models/class.js +3 -2
- package/dist/models/class.js.map +1 -1
- package/dist/models/event.d.ts +16 -0
- package/dist/models/event.d.ts.map +1 -1
- package/dist/models/fees.d.ts +554 -0
- package/dist/models/fees.d.ts.map +1 -0
- package/dist/models/fees.js +278 -0
- package/dist/models/fees.js.map +1 -0
- package/dist/models/file.d.ts +29 -0
- package/dist/models/file.d.ts.map +1 -1
- package/dist/models/folder.d.ts +2 -0
- package/dist/models/folder.d.ts.map +1 -1
- package/dist/models/labChat.d.ts +2 -0
- package/dist/models/labChat.d.ts.map +1 -1
- package/dist/models/marketplace.d.ts +180 -0
- package/dist/models/marketplace.d.ts.map +1 -0
- package/dist/models/marketplace.js +116 -0
- package/dist/models/marketplace.js.map +1 -0
- package/dist/models/moduleItem.d.ts +308 -0
- package/dist/models/moduleItem.d.ts.map +1 -0
- package/dist/models/moduleItem.js +93 -0
- package/dist/models/moduleItem.js.map +1 -0
- package/dist/models/newtonChat.d.ts +1 -0
- package/dist/models/newtonChat.d.ts.map +1 -1
- package/dist/models/school.d.ts +18 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +7 -2
- package/dist/models/school.js.map +1 -1
- package/dist/models/section.d.ts +4 -0
- package/dist/models/section.d.ts.map +1 -1
- package/dist/models/worksheet.d.ts +19 -0
- package/dist/models/worksheet.d.ts.map +1 -1
- package/dist/models/worksheet.js +2 -2
- package/dist/models/worksheet.js.map +1 -1
- package/dist/routers/_app.d.ts +12024 -7778
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +12 -2
- package/dist/routers/_app.js.map +1 -1
- package/dist/routers/academicCalendar.d.ts +229 -0
- package/dist/routers/academicCalendar.d.ts.map +1 -0
- package/dist/routers/academicCalendar.js +75 -0
- package/dist/routers/academicCalendar.js.map +1 -0
- package/dist/routers/admin.d.ts +2 -0
- package/dist/routers/admin.d.ts.map +1 -1
- package/dist/routers/agenda.d.ts +8 -0
- package/dist/routers/agenda.d.ts.map +1 -1
- package/dist/routers/assignment.d.ts +75 -0
- package/dist/routers/assignment.d.ts.map +1 -1
- package/dist/routers/assignment.js +10 -2
- package/dist/routers/assignment.js.map +1 -1
- package/dist/routers/attendance.d.ts +103 -0
- package/dist/routers/attendance.d.ts.map +1 -1
- package/dist/routers/attendance.js +8 -5
- package/dist/routers/attendance.js.map +1 -1
- package/dist/routers/class.d.ts +27 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +6 -2
- package/dist/routers/class.js.map +1 -1
- package/dist/routers/event.d.ts +8 -0
- package/dist/routers/event.d.ts.map +1 -1
- package/dist/routers/fees.d.ts +513 -0
- package/dist/routers/fees.d.ts.map +1 -0
- package/dist/routers/fees.js +88 -0
- package/dist/routers/fees.js.map +1 -0
- package/dist/routers/file.d.ts +2 -0
- package/dist/routers/file.d.ts.map +1 -1
- package/dist/routers/gradebook.d.ts +4 -1
- package/dist/routers/gradebook.d.ts.map +1 -1
- package/dist/routers/marketplace.d.ts +371 -0
- package/dist/routers/marketplace.d.ts.map +1 -0
- package/dist/routers/marketplace.js +87 -0
- package/dist/routers/marketplace.js.map +1 -0
- package/dist/routers/moduleItem.d.ts +263 -0
- package/dist/routers/moduleItem.d.ts.map +1 -0
- package/dist/routers/moduleItem.js +61 -0
- package/dist/routers/moduleItem.js.map +1 -0
- package/dist/routers/parent.d.ts +170 -0
- package/dist/routers/parent.d.ts.map +1 -0
- package/dist/routers/parent.js +49 -0
- package/dist/routers/parent.js.map +1 -0
- package/dist/routers/school.d.ts +363 -7
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +110 -3
- package/dist/routers/school.js.map +1 -1
- package/dist/routers/worksheet.d.ts +27 -0
- package/dist/routers/worksheet.d.ts.map +1 -1
- package/dist/routers/worksheet.js +10 -3
- package/dist/routers/worksheet.js.map +1 -1
- package/dist/services/academicCalendar.d.ts +173 -0
- package/dist/services/academicCalendar.d.ts.map +1 -0
- package/dist/services/academicCalendar.js +242 -0
- package/dist/services/academicCalendar.js.map +1 -0
- package/dist/services/adminInstitution.d.ts +2 -0
- package/dist/services/adminInstitution.d.ts.map +1 -1
- package/dist/services/agenda.d.ts +8 -0
- package/dist/services/agenda.d.ts.map +1 -1
- package/dist/services/assignment.d.ts +75 -0
- package/dist/services/assignment.d.ts.map +1 -1
- package/dist/services/assignment.js +91 -12
- package/dist/services/assignment.js.map +1 -1
- package/dist/services/attendance.d.ts +327 -10
- package/dist/services/attendance.d.ts.map +1 -1
- package/dist/services/attendance.js +389 -25
- package/dist/services/attendance.js.map +1 -1
- package/dist/services/attendanceMerge.d.ts +53 -0
- package/dist/services/attendanceMerge.d.ts.map +1 -0
- package/dist/services/attendanceMerge.js +87 -0
- package/dist/services/attendanceMerge.js.map +1 -0
- package/dist/services/attendanceRate.d.ts +24 -0
- package/dist/services/attendanceRate.d.ts.map +1 -0
- package/dist/services/attendanceRate.js +31 -0
- package/dist/services/attendanceRate.js.map +1 -0
- package/dist/services/attendanceStats.d.ts +22 -0
- package/dist/services/attendanceStats.d.ts.map +1 -0
- package/dist/services/attendanceStats.js +45 -0
- package/dist/services/attendanceStats.js.map +1 -0
- package/dist/services/class.d.ts +34 -2
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +27 -4
- package/dist/services/class.js.map +1 -1
- package/dist/services/event.d.ts +8 -0
- package/dist/services/event.d.ts.map +1 -1
- package/dist/services/fees.d.ts +422 -0
- package/dist/services/fees.d.ts.map +1 -0
- package/dist/services/fees.js +330 -0
- package/dist/services/fees.js.map +1 -0
- package/dist/services/feesAuthz.d.ts +46 -0
- package/dist/services/feesAuthz.d.ts.map +1 -0
- package/dist/services/feesAuthz.js +49 -0
- package/dist/services/feesAuthz.js.map +1 -0
- package/dist/services/file.d.ts +4 -0
- package/dist/services/file.d.ts.map +1 -1
- package/dist/services/gradebook.d.ts +18 -1
- package/dist/services/gradebook.d.ts.map +1 -1
- package/dist/services/gradebook.js +57 -2
- package/dist/services/gradebook.js.map +1 -1
- package/dist/services/idCard.d.ts +11 -0
- package/dist/services/idCard.d.ts.map +1 -0
- package/dist/services/idCard.js +122 -0
- package/dist/services/idCard.js.map +1 -0
- package/dist/services/marketplace.d.ts +301 -0
- package/dist/services/marketplace.d.ts.map +1 -0
- package/dist/services/marketplace.js +631 -0
- package/dist/services/marketplace.js.map +1 -0
- package/dist/services/moduleItem.d.ts +196 -0
- package/dist/services/moduleItem.d.ts.map +1 -0
- package/dist/services/moduleItem.js +204 -0
- package/dist/services/moduleItem.js.map +1 -0
- package/dist/services/notification.d.ts +58 -0
- package/dist/services/notification.d.ts.map +1 -1
- package/dist/services/notification.js +30 -2
- package/dist/services/notification.js.map +1 -1
- package/dist/services/parentPortal.d.ts +131 -0
- package/dist/services/parentPortal.d.ts.map +1 -0
- package/dist/services/parentPortal.js +314 -0
- package/dist/services/parentPortal.js.map +1 -0
- package/dist/services/school.d.ts +28 -3
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +98 -54
- package/dist/services/school.js.map +1 -1
- package/dist/services/studentImport.d.ts +34 -0
- package/dist/services/studentImport.d.ts.map +1 -0
- package/dist/services/studentImport.js +249 -0
- package/dist/services/studentImport.js.map +1 -0
- package/dist/services/transcript.d.ts +60 -0
- package/dist/services/transcript.d.ts.map +1 -0
- package/dist/services/transcript.js +231 -0
- package/dist/services/transcript.js.map +1 -0
- package/dist/services/worksheet.d.ts +22 -0
- package/dist/services/worksheet.d.ts.map +1 -1
- package/dist/services/worksheet.js +94 -2
- package/dist/services/worksheet.js.map +1 -1
- package/dist/trpc.d.ts +1 -1
- package/dist/trpc.d.ts.map +1 -1
- package/dist/trpc.js +5 -5
- package/dist/trpc.js.map +1 -1
- package/dist/utils/adminPanelAccess.d.ts +4 -5
- package/dist/utils/adminPanelAccess.d.ts.map +1 -1
- package/dist/utils/adminPanelAccess.js +12 -34
- package/dist/utils/adminPanelAccess.js.map +1 -1
- package/package.json +2 -1
- package/prisma/migrations/20260804120000_add_attendance_records_and_excusals/migration.sql +114 -0
- package/prisma/migrations/20260804120100_add_fee_plans_invoices_payments/migration.sql +101 -0
- package/prisma/migrations/20260804120200_add_marketplace_listings/migration.sql +54 -0
- package/prisma/migrations/20260804120300_content_platform_b3_b9/migration.sql +63 -0
- package/prisma/migrations/20260804120400_c_academic_ops/migration.sql +77 -0
- package/prisma/schema.prisma +371 -11
- package/scripts/import-nepal-cdc-curriculum.ts +402 -0
- package/src/lib/assignmentTypeDefaults.ts +222 -0
- package/src/lib/config/env.ts +0 -3
- package/src/lib/feePdf.ts +199 -0
- package/src/lib/feePdfFormat.ts +14 -0
- package/src/lib/idCardPdf.ts +336 -0
- package/src/lib/pdfLetterhead.ts +124 -0
- package/src/lib/transcriptPdf.ts +502 -0
- package/src/models/academicCalendar.ts +139 -0
- package/src/models/attendance.ts +702 -73
- package/src/models/class.ts +1 -0
- package/src/models/fees.ts +349 -0
- package/src/models/marketplace.ts +161 -0
- package/src/models/moduleItem.ts +125 -0
- package/src/models/school.ts +6 -0
- package/src/models/worksheet.ts +2 -0
- package/src/routers/_app.ts +10 -0
- package/src/routers/academicCalendar.ts +103 -0
- package/src/routers/assignment.ts +8 -0
- package/src/routers/attendance.ts +7 -3
- package/src/routers/class.ts +4 -0
- package/src/routers/fees.ts +126 -0
- package/src/routers/marketplace.ts +134 -0
- package/src/routers/moduleItem.ts +91 -0
- package/src/routers/parent.ts +68 -0
- package/src/routers/school.ts +149 -1
- package/src/routers/worksheet.ts +11 -0
- package/src/services/academicCalendar.ts +327 -0
- package/src/services/assignment.ts +122 -12
- package/src/services/attendance.ts +552 -26
- package/src/services/attendanceMerge.ts +139 -0
- package/src/services/attendanceRate.ts +41 -0
- package/src/services/attendanceStats.ts +63 -0
- package/src/services/class.ts +26 -2
- package/src/services/fees.ts +447 -0
- package/src/services/feesAuthz.ts +73 -0
- package/src/services/gradebook.ts +70 -0
- package/src/services/idCard.ts +147 -0
- package/src/services/marketplace.ts +790 -0
- package/src/services/moduleItem.ts +298 -0
- package/src/services/notification.ts +43 -0
- package/src/services/parentPortal.ts +367 -0
- package/src/services/school.ts +100 -54
- package/src/services/studentImport.ts +285 -0
- package/src/services/transcript.ts +301 -0
- package/src/services/worksheet.ts +111 -0
- package/src/trpc.ts +3 -3
- package/src/utils/adminPanelAccess.ts +12 -35
- package/tests/unit/assignmentTypeDefaults.test.ts +52 -0
- package/tests/unit/lib/feePdf.test.ts +15 -0
- package/tests/unit/lib/idCardPdf.test.ts +40 -0
- package/tests/unit/lib/transcriptPdf.test.ts +111 -0
- package/tests/unit/services/attendanceMerge.test.ts +90 -0
- package/tests/unit/services/attendanceRate.test.ts +38 -0
- package/tests/unit/services/attendanceStats.test.ts +54 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for merging teacher attendance updates with excusal locks.
|
|
3
|
+
*/
|
|
4
|
+
import type { AttendanceStatus } from "@prisma/client";
|
|
5
|
+
|
|
6
|
+
export type StudentRef = { id: string; username: string };
|
|
7
|
+
|
|
8
|
+
export type AttendanceLock = {
|
|
9
|
+
studentId: string;
|
|
10
|
+
excusalId: string;
|
|
11
|
+
reason?: string | null;
|
|
12
|
+
note?: string | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type MergedAttendanceUpdate = {
|
|
16
|
+
presentIds: string[];
|
|
17
|
+
lateIds: string[];
|
|
18
|
+
absentIds: string[];
|
|
19
|
+
excusedIds: string[];
|
|
20
|
+
records: Array<{
|
|
21
|
+
studentId: string;
|
|
22
|
+
status: AttendanceStatus;
|
|
23
|
+
note?: string | null;
|
|
24
|
+
excusedByExcusalId?: string | null;
|
|
25
|
+
updatedById?: string | null;
|
|
26
|
+
}>;
|
|
27
|
+
/** Students whose status was forced to EXCUSED by an active excusal. */
|
|
28
|
+
lockedStudentIds: string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build present/late/absent/excused lists + per-student records.
|
|
33
|
+
* Active excusal locks always win (status EXCUSED + excusedByExcusalId set).
|
|
34
|
+
*/
|
|
35
|
+
export function mergeAttendanceUpdateWithLocks(input: {
|
|
36
|
+
present: StudentRef[];
|
|
37
|
+
late: StudentRef[];
|
|
38
|
+
absent: StudentRef[];
|
|
39
|
+
excused?: StudentRef[];
|
|
40
|
+
locks: AttendanceLock[];
|
|
41
|
+
updatedById?: string | null;
|
|
42
|
+
/** Preserve notes for locked students when available. */
|
|
43
|
+
existingNotesByStudentId?: Map<string, string | null | undefined>;
|
|
44
|
+
}): MergedAttendanceUpdate {
|
|
45
|
+
const lockByStudent = new Map(input.locks.map((lock) => [lock.studentId, lock]));
|
|
46
|
+
const lockedStudentIds = [...lockByStudent.keys()];
|
|
47
|
+
|
|
48
|
+
const stripLocked = (students: StudentRef[]) =>
|
|
49
|
+
students.filter((s) => !lockByStudent.has(s.id));
|
|
50
|
+
|
|
51
|
+
const presentIds = stripLocked(input.present).map((s) => s.id);
|
|
52
|
+
const lateIds = stripLocked(input.late).map((s) => s.id);
|
|
53
|
+
const absentIds = stripLocked(input.absent).map((s) => s.id);
|
|
54
|
+
const manualExcusedIds = stripLocked(input.excused ?? []).map((s) => s.id);
|
|
55
|
+
const excusedIds = [...new Set([...manualExcusedIds, ...lockedStudentIds])];
|
|
56
|
+
|
|
57
|
+
const records: MergedAttendanceUpdate["records"] = [];
|
|
58
|
+
|
|
59
|
+
for (const studentId of presentIds) {
|
|
60
|
+
records.push({
|
|
61
|
+
studentId,
|
|
62
|
+
status: "PRESENT",
|
|
63
|
+
updatedById: input.updatedById,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
for (const studentId of lateIds) {
|
|
67
|
+
records.push({
|
|
68
|
+
studentId,
|
|
69
|
+
status: "LATE",
|
|
70
|
+
updatedById: input.updatedById,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
for (const studentId of absentIds) {
|
|
74
|
+
records.push({
|
|
75
|
+
studentId,
|
|
76
|
+
status: "ABSENT",
|
|
77
|
+
updatedById: input.updatedById,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
for (const studentId of manualExcusedIds) {
|
|
81
|
+
records.push({
|
|
82
|
+
studentId,
|
|
83
|
+
status: "EXCUSED",
|
|
84
|
+
excusedByExcusalId: null,
|
|
85
|
+
updatedById: input.updatedById,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
for (const lock of input.locks) {
|
|
89
|
+
records.push({
|
|
90
|
+
studentId: lock.studentId,
|
|
91
|
+
status: "EXCUSED",
|
|
92
|
+
note:
|
|
93
|
+
input.existingNotesByStudentId?.get(lock.studentId) ?? lock.note ?? null,
|
|
94
|
+
excusedByExcusalId: lock.excusalId,
|
|
95
|
+
updatedById: input.updatedById,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
presentIds,
|
|
101
|
+
lateIds,
|
|
102
|
+
absentIds,
|
|
103
|
+
excusedIds,
|
|
104
|
+
records,
|
|
105
|
+
lockedStudentIds,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Map active excusals (with student join rows) to per-student locks. */
|
|
110
|
+
export function buildExcusalLocks(
|
|
111
|
+
excusals: Array<{
|
|
112
|
+
id: string;
|
|
113
|
+
reason?: string | null;
|
|
114
|
+
note?: string | null;
|
|
115
|
+
students: Array<{ studentId: string }>;
|
|
116
|
+
}>,
|
|
117
|
+
studentIds?: Iterable<string>
|
|
118
|
+
): AttendanceLock[] {
|
|
119
|
+
const allow =
|
|
120
|
+
studentIds === undefined ? null : new Set(studentIds);
|
|
121
|
+
const locks: AttendanceLock[] = [];
|
|
122
|
+
const seen = new Set<string>();
|
|
123
|
+
|
|
124
|
+
for (const excusal of excusals) {
|
|
125
|
+
for (const row of excusal.students) {
|
|
126
|
+
if (allow && !allow.has(row.studentId)) continue;
|
|
127
|
+
if (seen.has(row.studentId)) continue;
|
|
128
|
+
seen.add(row.studentId);
|
|
129
|
+
locks.push({
|
|
130
|
+
studentId: row.studentId,
|
|
131
|
+
excusalId: excusal.id,
|
|
132
|
+
reason: excusal.reason,
|
|
133
|
+
note: excusal.note,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return locks;
|
|
139
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attendance rate helpers for transcripts / progress reports.
|
|
3
|
+
*
|
|
4
|
+
* Reads the current Attendance M2M shape only (present / late / absent).
|
|
5
|
+
* Do NOT write Attendance schema here — A1 may introduce EXCUSED /
|
|
6
|
+
* AttendanceRecord; keep rate logic in this one place so A1 can extend it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type AttendanceStatus = "PRESENT" | "LATE" | "ABSENT";
|
|
10
|
+
|
|
11
|
+
export interface AttendanceRateSummary {
|
|
12
|
+
present: number;
|
|
13
|
+
late: number;
|
|
14
|
+
absent: number;
|
|
15
|
+
total: number;
|
|
16
|
+
/** (present + late) / total * 100, or null when total is 0. */
|
|
17
|
+
percent: number | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Computes attendance counts and rate from shaped status records.
|
|
22
|
+
* Late counts toward attendance (present for rate purposes).
|
|
23
|
+
*/
|
|
24
|
+
export function computeAttendanceRate(
|
|
25
|
+
records: { status: AttendanceStatus }[]
|
|
26
|
+
): AttendanceRateSummary {
|
|
27
|
+
let present = 0;
|
|
28
|
+
let late = 0;
|
|
29
|
+
let absent = 0;
|
|
30
|
+
|
|
31
|
+
for (const record of records) {
|
|
32
|
+
if (record.status === "PRESENT") present += 1;
|
|
33
|
+
else if (record.status === "LATE") late += 1;
|
|
34
|
+
else if (record.status === "ABSENT") absent += 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const total = present + late + absent;
|
|
38
|
+
const percent = total === 0 ? null : ((present + late) / total) * 100;
|
|
39
|
+
|
|
40
|
+
return { present, late, absent, total, percent };
|
|
41
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure attendance percentage helpers.
|
|
3
|
+
* EXCUSED days are excluded from both numerator and denominator.
|
|
4
|
+
*/
|
|
5
|
+
import type { AttendanceStatus } from "@prisma/client";
|
|
6
|
+
|
|
7
|
+
export type AttendanceStatusCount = {
|
|
8
|
+
present: number;
|
|
9
|
+
late: number;
|
|
10
|
+
absent: number;
|
|
11
|
+
excused: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function emptyAttendanceCounts(): AttendanceStatusCount {
|
|
15
|
+
return { present: 0, late: 0, absent: 0, excused: 0 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function tallyAttendanceStatuses(
|
|
19
|
+
statuses: Iterable<AttendanceStatus | string>
|
|
20
|
+
): AttendanceStatusCount {
|
|
21
|
+
const counts = emptyAttendanceCounts();
|
|
22
|
+
for (const status of statuses) {
|
|
23
|
+
switch (status) {
|
|
24
|
+
case "PRESENT":
|
|
25
|
+
counts.present += 1;
|
|
26
|
+
break;
|
|
27
|
+
case "LATE":
|
|
28
|
+
counts.late += 1;
|
|
29
|
+
break;
|
|
30
|
+
case "ABSENT":
|
|
31
|
+
counts.absent += 1;
|
|
32
|
+
break;
|
|
33
|
+
case "EXCUSED":
|
|
34
|
+
counts.excused += 1;
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return counts;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Attendance rate as a percentage.
|
|
45
|
+
* Formula: (present + late) / (present + late + absent) * 100
|
|
46
|
+
* Excused records are ignored entirely.
|
|
47
|
+
* Returns null when there are no countable (non-excused) records.
|
|
48
|
+
*/
|
|
49
|
+
export function computeAttendancePercentage(
|
|
50
|
+
counts: Pick<AttendanceStatusCount, "present" | "late" | "absent">
|
|
51
|
+
): number | null {
|
|
52
|
+
const countable = counts.present + counts.late + counts.absent;
|
|
53
|
+
if (countable === 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return ((counts.present + counts.late) / countable) * 100;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function computeAttendancePercentageFromStatuses(
|
|
60
|
+
statuses: Iterable<AttendanceStatus | string>
|
|
61
|
+
): number | null {
|
|
62
|
+
return computeAttendancePercentage(tallyAttendanceStatuses(statuses));
|
|
63
|
+
}
|
package/src/services/class.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Class service – CRUD for classes, members, grades, mark schemes, grading boundaries,
|
|
3
3
|
* syllabus, lab drafts, and invite codes.
|
|
4
4
|
*/
|
|
5
|
-
import { SessionType } from "@prisma/client";
|
|
5
|
+
import { Prisma, SessionType } from "@prisma/client";
|
|
6
6
|
import { TRPCError } from "@trpc/server";
|
|
7
7
|
import { prisma } from "../lib/prisma.js";
|
|
8
8
|
import { generateInviteCode } from "../utils/generateInviteCode.js";
|
|
@@ -196,6 +196,7 @@ export async function updateClass(
|
|
|
196
196
|
discussionsEnabled?: boolean;
|
|
197
197
|
studentsCanCreateDiscussions?: boolean;
|
|
198
198
|
studentsCanReply?: boolean;
|
|
199
|
+
sidebarFeatures?: Record<string, boolean> | null;
|
|
199
200
|
}
|
|
200
201
|
) {
|
|
201
202
|
if (data.departmentId !== undefined) {
|
|
@@ -226,7 +227,29 @@ export async function updateClass(
|
|
|
226
227
|
|
|
227
228
|
const updatedClass = await prisma.class.update({
|
|
228
229
|
where: { id: classId },
|
|
229
|
-
|
|
230
|
+
// Prisma Exact<> rejects mixed relation/unchecked shapes; pass unchecked scalars.
|
|
231
|
+
data: {
|
|
232
|
+
...(data.name !== undefined && { name: data.name }),
|
|
233
|
+
...(data.section !== undefined && { section: data.section }),
|
|
234
|
+
...(data.subject !== undefined && { subject: data.subject }),
|
|
235
|
+
...(data.color !== undefined && { color: data.color }),
|
|
236
|
+
...(data.symbol !== undefined && { symbol: data.symbol }),
|
|
237
|
+
...(data.departmentId !== undefined && {
|
|
238
|
+
departmentId: data.departmentId,
|
|
239
|
+
}),
|
|
240
|
+
...(data.discussionsEnabled !== undefined && {
|
|
241
|
+
discussionsEnabled: data.discussionsEnabled,
|
|
242
|
+
}),
|
|
243
|
+
...(data.studentsCanCreateDiscussions !== undefined && {
|
|
244
|
+
studentsCanCreateDiscussions: data.studentsCanCreateDiscussions,
|
|
245
|
+
}),
|
|
246
|
+
...(data.studentsCanReply !== undefined && {
|
|
247
|
+
studentsCanReply: data.studentsCanReply,
|
|
248
|
+
}),
|
|
249
|
+
...(data.sidebarFeatures !== undefined && {
|
|
250
|
+
sidebarFeatures: data.sidebarFeatures ?? Prisma.DbNull,
|
|
251
|
+
}),
|
|
252
|
+
} as Parameters<typeof prisma.class.update>[0]["data"],
|
|
230
253
|
select: {
|
|
231
254
|
id: true,
|
|
232
255
|
name: true,
|
|
@@ -238,6 +261,7 @@ export async function updateClass(
|
|
|
238
261
|
discussionsEnabled: true,
|
|
239
262
|
studentsCanCreateDiscussions: true,
|
|
240
263
|
studentsCanReply: true,
|
|
264
|
+
sidebarFeatures: true,
|
|
241
265
|
},
|
|
242
266
|
});
|
|
243
267
|
return { updatedClass };
|