@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.
Files changed (298) hide show
  1. package/.env.example +0 -1
  2. package/data/curriculum/nepal-cdc-g4-g5/README.md +90 -0
  3. package/data/curriculum/nepal-cdc-g4-g5/manifest.json +97 -0
  4. package/data/curriculum/nepal-cdc-g4-g5/proficiency-scale.json +76 -0
  5. package/data/curriculum/nepal-cdc-g4-g5/subjects/english.json +6936 -0
  6. package/data/curriculum/nepal-cdc-g4-g5/subjects/health-physical-creative-arts.json +3401 -0
  7. package/data/curriculum/nepal-cdc-g4-g5/subjects/mathematics.json +1511 -0
  8. package/data/curriculum/nepal-cdc-g4-g5/subjects/nepali.json +7928 -0
  9. package/data/curriculum/nepal-cdc-g4-g5/subjects/science-technology.json +2420 -0
  10. package/data/curriculum/nepal-cdc-g4-g5/subjects/social-studies.json +2177 -0
  11. package/dist/lib/assignmentTypeDefaults.d.ts +41 -0
  12. package/dist/lib/assignmentTypeDefaults.d.ts.map +1 -0
  13. package/dist/lib/assignmentTypeDefaults.js +167 -0
  14. package/dist/lib/assignmentTypeDefaults.js.map +1 -0
  15. package/dist/lib/config/env.d.ts +0 -8
  16. package/dist/lib/config/env.d.ts.map +1 -1
  17. package/dist/lib/config/env.js +2 -5
  18. package/dist/lib/config/env.js.map +1 -1
  19. package/dist/lib/feePdf.d.ts +19 -0
  20. package/dist/lib/feePdf.d.ts.map +1 -0
  21. package/dist/lib/feePdf.js +167 -0
  22. package/dist/lib/feePdf.js.map +1 -0
  23. package/dist/lib/feePdfFormat.d.ts +4 -0
  24. package/dist/lib/feePdfFormat.d.ts.map +1 -0
  25. package/dist/lib/feePdfFormat.js +16 -0
  26. package/dist/lib/feePdfFormat.js.map +1 -0
  27. package/dist/lib/idCardPdf.d.ts +26 -0
  28. package/dist/lib/idCardPdf.d.ts.map +1 -0
  29. package/dist/lib/idCardPdf.js +272 -0
  30. package/dist/lib/idCardPdf.js.map +1 -0
  31. package/dist/lib/pdfLetterhead.d.ts +41 -0
  32. package/dist/lib/pdfLetterhead.d.ts.map +1 -0
  33. package/dist/lib/pdfLetterhead.js +86 -0
  34. package/dist/lib/pdfLetterhead.js.map +1 -0
  35. package/dist/lib/transcriptPdf.d.ts +55 -0
  36. package/dist/lib/transcriptPdf.d.ts.map +1 -0
  37. package/dist/lib/transcriptPdf.js +303 -0
  38. package/dist/lib/transcriptPdf.js.map +1 -0
  39. package/dist/models/academicCalendar.d.ts +198 -0
  40. package/dist/models/academicCalendar.d.ts.map +1 -0
  41. package/dist/models/academicCalendar.js +106 -0
  42. package/dist/models/academicCalendar.js.map +1 -0
  43. package/dist/models/agenda.d.ts +8 -0
  44. package/dist/models/agenda.d.ts.map +1 -1
  45. package/dist/models/announcement.d.ts +2 -0
  46. package/dist/models/announcement.d.ts.map +1 -1
  47. package/dist/models/assignment.d.ts +66 -0
  48. package/dist/models/assignment.d.ts.map +1 -1
  49. package/dist/models/attendance.d.ts +659 -21
  50. package/dist/models/attendance.d.ts.map +1 -1
  51. package/dist/models/attendance.js +547 -75
  52. package/dist/models/attendance.js.map +1 -1
  53. package/dist/models/auth.d.ts +2 -0
  54. package/dist/models/auth.d.ts.map +1 -1
  55. package/dist/models/class.d.ts +12 -0
  56. package/dist/models/class.d.ts.map +1 -1
  57. package/dist/models/class.js +3 -2
  58. package/dist/models/class.js.map +1 -1
  59. package/dist/models/event.d.ts +16 -0
  60. package/dist/models/event.d.ts.map +1 -1
  61. package/dist/models/fees.d.ts +554 -0
  62. package/dist/models/fees.d.ts.map +1 -0
  63. package/dist/models/fees.js +278 -0
  64. package/dist/models/fees.js.map +1 -0
  65. package/dist/models/file.d.ts +29 -0
  66. package/dist/models/file.d.ts.map +1 -1
  67. package/dist/models/folder.d.ts +2 -0
  68. package/dist/models/folder.d.ts.map +1 -1
  69. package/dist/models/labChat.d.ts +2 -0
  70. package/dist/models/labChat.d.ts.map +1 -1
  71. package/dist/models/marketplace.d.ts +180 -0
  72. package/dist/models/marketplace.d.ts.map +1 -0
  73. package/dist/models/marketplace.js +116 -0
  74. package/dist/models/marketplace.js.map +1 -0
  75. package/dist/models/moduleItem.d.ts +308 -0
  76. package/dist/models/moduleItem.d.ts.map +1 -0
  77. package/dist/models/moduleItem.js +93 -0
  78. package/dist/models/moduleItem.js.map +1 -0
  79. package/dist/models/newtonChat.d.ts +1 -0
  80. package/dist/models/newtonChat.d.ts.map +1 -1
  81. package/dist/models/school.d.ts +18 -0
  82. package/dist/models/school.d.ts.map +1 -1
  83. package/dist/models/school.js +7 -2
  84. package/dist/models/school.js.map +1 -1
  85. package/dist/models/section.d.ts +4 -0
  86. package/dist/models/section.d.ts.map +1 -1
  87. package/dist/models/worksheet.d.ts +19 -0
  88. package/dist/models/worksheet.d.ts.map +1 -1
  89. package/dist/models/worksheet.js +2 -2
  90. package/dist/models/worksheet.js.map +1 -1
  91. package/dist/routers/_app.d.ts +12024 -7778
  92. package/dist/routers/_app.d.ts.map +1 -1
  93. package/dist/routers/_app.js +12 -2
  94. package/dist/routers/_app.js.map +1 -1
  95. package/dist/routers/academicCalendar.d.ts +229 -0
  96. package/dist/routers/academicCalendar.d.ts.map +1 -0
  97. package/dist/routers/academicCalendar.js +75 -0
  98. package/dist/routers/academicCalendar.js.map +1 -0
  99. package/dist/routers/admin.d.ts +2 -0
  100. package/dist/routers/admin.d.ts.map +1 -1
  101. package/dist/routers/agenda.d.ts +8 -0
  102. package/dist/routers/agenda.d.ts.map +1 -1
  103. package/dist/routers/assignment.d.ts +75 -0
  104. package/dist/routers/assignment.d.ts.map +1 -1
  105. package/dist/routers/assignment.js +10 -2
  106. package/dist/routers/assignment.js.map +1 -1
  107. package/dist/routers/attendance.d.ts +103 -0
  108. package/dist/routers/attendance.d.ts.map +1 -1
  109. package/dist/routers/attendance.js +8 -5
  110. package/dist/routers/attendance.js.map +1 -1
  111. package/dist/routers/class.d.ts +27 -0
  112. package/dist/routers/class.d.ts.map +1 -1
  113. package/dist/routers/class.js +6 -2
  114. package/dist/routers/class.js.map +1 -1
  115. package/dist/routers/event.d.ts +8 -0
  116. package/dist/routers/event.d.ts.map +1 -1
  117. package/dist/routers/fees.d.ts +513 -0
  118. package/dist/routers/fees.d.ts.map +1 -0
  119. package/dist/routers/fees.js +88 -0
  120. package/dist/routers/fees.js.map +1 -0
  121. package/dist/routers/file.d.ts +2 -0
  122. package/dist/routers/file.d.ts.map +1 -1
  123. package/dist/routers/gradebook.d.ts +4 -1
  124. package/dist/routers/gradebook.d.ts.map +1 -1
  125. package/dist/routers/marketplace.d.ts +371 -0
  126. package/dist/routers/marketplace.d.ts.map +1 -0
  127. package/dist/routers/marketplace.js +87 -0
  128. package/dist/routers/marketplace.js.map +1 -0
  129. package/dist/routers/moduleItem.d.ts +263 -0
  130. package/dist/routers/moduleItem.d.ts.map +1 -0
  131. package/dist/routers/moduleItem.js +61 -0
  132. package/dist/routers/moduleItem.js.map +1 -0
  133. package/dist/routers/parent.d.ts +170 -0
  134. package/dist/routers/parent.d.ts.map +1 -0
  135. package/dist/routers/parent.js +49 -0
  136. package/dist/routers/parent.js.map +1 -0
  137. package/dist/routers/school.d.ts +363 -7
  138. package/dist/routers/school.d.ts.map +1 -1
  139. package/dist/routers/school.js +110 -3
  140. package/dist/routers/school.js.map +1 -1
  141. package/dist/routers/worksheet.d.ts +27 -0
  142. package/dist/routers/worksheet.d.ts.map +1 -1
  143. package/dist/routers/worksheet.js +10 -3
  144. package/dist/routers/worksheet.js.map +1 -1
  145. package/dist/services/academicCalendar.d.ts +173 -0
  146. package/dist/services/academicCalendar.d.ts.map +1 -0
  147. package/dist/services/academicCalendar.js +242 -0
  148. package/dist/services/academicCalendar.js.map +1 -0
  149. package/dist/services/adminInstitution.d.ts +2 -0
  150. package/dist/services/adminInstitution.d.ts.map +1 -1
  151. package/dist/services/agenda.d.ts +8 -0
  152. package/dist/services/agenda.d.ts.map +1 -1
  153. package/dist/services/assignment.d.ts +75 -0
  154. package/dist/services/assignment.d.ts.map +1 -1
  155. package/dist/services/assignment.js +91 -12
  156. package/dist/services/assignment.js.map +1 -1
  157. package/dist/services/attendance.d.ts +327 -10
  158. package/dist/services/attendance.d.ts.map +1 -1
  159. package/dist/services/attendance.js +389 -25
  160. package/dist/services/attendance.js.map +1 -1
  161. package/dist/services/attendanceMerge.d.ts +53 -0
  162. package/dist/services/attendanceMerge.d.ts.map +1 -0
  163. package/dist/services/attendanceMerge.js +87 -0
  164. package/dist/services/attendanceMerge.js.map +1 -0
  165. package/dist/services/attendanceRate.d.ts +24 -0
  166. package/dist/services/attendanceRate.d.ts.map +1 -0
  167. package/dist/services/attendanceRate.js +31 -0
  168. package/dist/services/attendanceRate.js.map +1 -0
  169. package/dist/services/attendanceStats.d.ts +22 -0
  170. package/dist/services/attendanceStats.d.ts.map +1 -0
  171. package/dist/services/attendanceStats.js +45 -0
  172. package/dist/services/attendanceStats.js.map +1 -0
  173. package/dist/services/class.d.ts +34 -2
  174. package/dist/services/class.d.ts.map +1 -1
  175. package/dist/services/class.js +27 -4
  176. package/dist/services/class.js.map +1 -1
  177. package/dist/services/event.d.ts +8 -0
  178. package/dist/services/event.d.ts.map +1 -1
  179. package/dist/services/fees.d.ts +422 -0
  180. package/dist/services/fees.d.ts.map +1 -0
  181. package/dist/services/fees.js +330 -0
  182. package/dist/services/fees.js.map +1 -0
  183. package/dist/services/feesAuthz.d.ts +46 -0
  184. package/dist/services/feesAuthz.d.ts.map +1 -0
  185. package/dist/services/feesAuthz.js +49 -0
  186. package/dist/services/feesAuthz.js.map +1 -0
  187. package/dist/services/file.d.ts +4 -0
  188. package/dist/services/file.d.ts.map +1 -1
  189. package/dist/services/gradebook.d.ts +18 -1
  190. package/dist/services/gradebook.d.ts.map +1 -1
  191. package/dist/services/gradebook.js +57 -2
  192. package/dist/services/gradebook.js.map +1 -1
  193. package/dist/services/idCard.d.ts +11 -0
  194. package/dist/services/idCard.d.ts.map +1 -0
  195. package/dist/services/idCard.js +122 -0
  196. package/dist/services/idCard.js.map +1 -0
  197. package/dist/services/marketplace.d.ts +301 -0
  198. package/dist/services/marketplace.d.ts.map +1 -0
  199. package/dist/services/marketplace.js +631 -0
  200. package/dist/services/marketplace.js.map +1 -0
  201. package/dist/services/moduleItem.d.ts +196 -0
  202. package/dist/services/moduleItem.d.ts.map +1 -0
  203. package/dist/services/moduleItem.js +204 -0
  204. package/dist/services/moduleItem.js.map +1 -0
  205. package/dist/services/notification.d.ts +58 -0
  206. package/dist/services/notification.d.ts.map +1 -1
  207. package/dist/services/notification.js +30 -2
  208. package/dist/services/notification.js.map +1 -1
  209. package/dist/services/parentPortal.d.ts +131 -0
  210. package/dist/services/parentPortal.d.ts.map +1 -0
  211. package/dist/services/parentPortal.js +314 -0
  212. package/dist/services/parentPortal.js.map +1 -0
  213. package/dist/services/school.d.ts +28 -3
  214. package/dist/services/school.d.ts.map +1 -1
  215. package/dist/services/school.js +98 -54
  216. package/dist/services/school.js.map +1 -1
  217. package/dist/services/studentImport.d.ts +34 -0
  218. package/dist/services/studentImport.d.ts.map +1 -0
  219. package/dist/services/studentImport.js +249 -0
  220. package/dist/services/studentImport.js.map +1 -0
  221. package/dist/services/transcript.d.ts +60 -0
  222. package/dist/services/transcript.d.ts.map +1 -0
  223. package/dist/services/transcript.js +231 -0
  224. package/dist/services/transcript.js.map +1 -0
  225. package/dist/services/worksheet.d.ts +22 -0
  226. package/dist/services/worksheet.d.ts.map +1 -1
  227. package/dist/services/worksheet.js +94 -2
  228. package/dist/services/worksheet.js.map +1 -1
  229. package/dist/trpc.d.ts +1 -1
  230. package/dist/trpc.d.ts.map +1 -1
  231. package/dist/trpc.js +5 -5
  232. package/dist/trpc.js.map +1 -1
  233. package/dist/utils/adminPanelAccess.d.ts +4 -5
  234. package/dist/utils/adminPanelAccess.d.ts.map +1 -1
  235. package/dist/utils/adminPanelAccess.js +12 -34
  236. package/dist/utils/adminPanelAccess.js.map +1 -1
  237. package/package.json +2 -1
  238. package/prisma/migrations/20260804120000_add_attendance_records_and_excusals/migration.sql +114 -0
  239. package/prisma/migrations/20260804120100_add_fee_plans_invoices_payments/migration.sql +101 -0
  240. package/prisma/migrations/20260804120200_add_marketplace_listings/migration.sql +54 -0
  241. package/prisma/migrations/20260804120300_content_platform_b3_b9/migration.sql +63 -0
  242. package/prisma/migrations/20260804120400_c_academic_ops/migration.sql +77 -0
  243. package/prisma/schema.prisma +371 -11
  244. package/scripts/import-nepal-cdc-curriculum.ts +402 -0
  245. package/src/lib/assignmentTypeDefaults.ts +222 -0
  246. package/src/lib/config/env.ts +0 -3
  247. package/src/lib/feePdf.ts +199 -0
  248. package/src/lib/feePdfFormat.ts +14 -0
  249. package/src/lib/idCardPdf.ts +336 -0
  250. package/src/lib/pdfLetterhead.ts +124 -0
  251. package/src/lib/transcriptPdf.ts +502 -0
  252. package/src/models/academicCalendar.ts +139 -0
  253. package/src/models/attendance.ts +702 -73
  254. package/src/models/class.ts +1 -0
  255. package/src/models/fees.ts +349 -0
  256. package/src/models/marketplace.ts +161 -0
  257. package/src/models/moduleItem.ts +125 -0
  258. package/src/models/school.ts +6 -0
  259. package/src/models/worksheet.ts +2 -0
  260. package/src/routers/_app.ts +10 -0
  261. package/src/routers/academicCalendar.ts +103 -0
  262. package/src/routers/assignment.ts +8 -0
  263. package/src/routers/attendance.ts +7 -3
  264. package/src/routers/class.ts +4 -0
  265. package/src/routers/fees.ts +126 -0
  266. package/src/routers/marketplace.ts +134 -0
  267. package/src/routers/moduleItem.ts +91 -0
  268. package/src/routers/parent.ts +68 -0
  269. package/src/routers/school.ts +149 -1
  270. package/src/routers/worksheet.ts +11 -0
  271. package/src/services/academicCalendar.ts +327 -0
  272. package/src/services/assignment.ts +122 -12
  273. package/src/services/attendance.ts +552 -26
  274. package/src/services/attendanceMerge.ts +139 -0
  275. package/src/services/attendanceRate.ts +41 -0
  276. package/src/services/attendanceStats.ts +63 -0
  277. package/src/services/class.ts +26 -2
  278. package/src/services/fees.ts +447 -0
  279. package/src/services/feesAuthz.ts +73 -0
  280. package/src/services/gradebook.ts +70 -0
  281. package/src/services/idCard.ts +147 -0
  282. package/src/services/marketplace.ts +790 -0
  283. package/src/services/moduleItem.ts +298 -0
  284. package/src/services/notification.ts +43 -0
  285. package/src/services/parentPortal.ts +367 -0
  286. package/src/services/school.ts +100 -54
  287. package/src/services/studentImport.ts +285 -0
  288. package/src/services/transcript.ts +301 -0
  289. package/src/services/worksheet.ts +111 -0
  290. package/src/trpc.ts +3 -3
  291. package/src/utils/adminPanelAccess.ts +12 -35
  292. package/tests/unit/assignmentTypeDefaults.test.ts +52 -0
  293. package/tests/unit/lib/feePdf.test.ts +15 -0
  294. package/tests/unit/lib/idCardPdf.test.ts +40 -0
  295. package/tests/unit/lib/transcriptPdf.test.ts +111 -0
  296. package/tests/unit/services/attendanceMerge.test.ts +90 -0
  297. package/tests/unit/services/attendanceRate.test.ts +38 -0
  298. package/tests/unit/services/attendanceStats.test.ts +54 -0
@@ -103,6 +103,10 @@ enum SchoolMembershipRole {
103
103
  ADMIN
104
104
  TEACHER
105
105
  STUDENT
106
+ /// Nepal-pilot: fee/payment operations (authz stubs until A2 fees land).
107
+ ACCOUNTANT
108
+ /// Nepal-pilot: guardian linked to one or more students via ParentStudentLink.
109
+ PARENT
106
110
  }
107
111
 
108
112
  enum SchoolMembershipStatus {
@@ -118,24 +122,99 @@ enum SchoolInviteStatus {
118
122
  EXPIRED
119
123
  }
120
124
 
125
+ /// Marketplace listing resource kinds for B1 v1.
126
+ /// Exercise/poll/kahoot types are reserved for a later milestone — do not add
127
+ /// them here until that product surface ships.
128
+ enum MarketplaceListingType {
129
+ MARKSCHEME
130
+ WORKSHEET
131
+ FILE
132
+ }
133
+
134
+ enum MarketplaceVisibility {
135
+ SCHOOL
136
+ PUBLIC
137
+ }
138
+
121
139
  model School {
122
140
  id String @id @default(uuid())
123
141
  name String
124
142
  description String?
125
143
  logo File? @relation("SchoolLogo", references: [id], fields: [logoId])
126
144
  logoId String?
145
+ /// Optional PDF/image letterhead used when stamping branded PDFs
146
+ /// (fee invoices/receipts, transcripts).
147
+ letterhead File? @relation("SchoolLetterhead", references: [id], fields: [letterheadFileId])
148
+ letterheadFileId String?
127
149
  subdomain String? @unique
128
150
  createdAt DateTime @default(now())
129
151
  updatedAt DateTime @updatedAt
130
152
 
131
- users User[]
132
- memberships SchoolMembership[]
133
- invites SchoolInvite[]
134
- classes Class[]
135
- departments Department[]
136
- tags Tag[]
137
- storageRoot Folder? @relation("SchoolStorageRoot")
138
- permissions Permission[] @relation("SchoolPermissions")
153
+ users User[]
154
+ memberships SchoolMembership[]
155
+ invites SchoolInvite[]
156
+ classes Class[]
157
+ departments Department[]
158
+ tags Tag[]
159
+ storageRoot Folder? @relation("SchoolStorageRoot")
160
+ permissions Permission[] @relation("SchoolPermissions")
161
+ excusals StudentExcusal[]
162
+ feePlans FeePlan[]
163
+ feeInvoices FeeInvoice[]
164
+ feePayments FeePayment[]
165
+ marketplaceListings MarketplaceListing[]
166
+ academicYears AcademicYear[]
167
+ parentLinks ParentStudentLink[]
168
+ }
169
+
170
+ /// School academic calendar year (e.g. "2025-2026" or "2082"). No BS calendar logic.
171
+ model AcademicYear {
172
+ id String @id @default(uuid())
173
+ schoolId String
174
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
175
+ name String
176
+ startDate DateTime
177
+ endDate DateTime
178
+ isCurrent Boolean @default(false)
179
+ createdAt DateTime @default(now())
180
+ updatedAt DateTime @updatedAt
181
+
182
+ terms AcademicTerm[]
183
+
184
+ @@unique([schoolId, name])
185
+ @@index([schoolId, isCurrent])
186
+ }
187
+
188
+ model AcademicTerm {
189
+ id String @id @default(uuid())
190
+ academicYearId String
191
+ academicYear AcademicYear @relation(fields: [academicYearId], references: [id], onDelete: Cascade)
192
+ name String
193
+ startDate DateTime
194
+ endDate DateTime
195
+ isCurrent Boolean @default(false)
196
+ createdAt DateTime @default(now())
197
+ updatedAt DateTime @updatedAt
198
+
199
+ @@unique([academicYearId, name])
200
+ @@index([academicYearId, isCurrent])
201
+ }
202
+
203
+ /// Links a PARENT membership user to a STUDENT membership user within a school.
204
+ model ParentStudentLink {
205
+ id String @id @default(uuid())
206
+ schoolId String
207
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
208
+ parentUserId String
209
+ parent User @relation("ParentLinks", fields: [parentUserId], references: [id], onDelete: Cascade)
210
+ studentUserId String
211
+ student User @relation("StudentParentLinks", fields: [studentUserId], references: [id], onDelete: Cascade)
212
+ createdAt DateTime @default(now())
213
+ updatedAt DateTime @updatedAt
214
+
215
+ @@unique([schoolId, parentUserId, studentUserId])
216
+ @@index([schoolId, parentUserId])
217
+ @@index([schoolId, studentUserId])
139
218
  }
140
219
 
141
220
  model Department {
@@ -218,10 +297,18 @@ model User {
218
297
  lateAttendance Attendance[] @relation("LateAttendance")
219
298
  absentAttendance Attendance[] @relation("AbsentAttendance")
220
299
 
300
+ attendanceRecords AttendanceRecord[] @relation("AttendanceRecordStudent")
301
+ updatedAttendanceRecords AttendanceRecord[] @relation("AttendanceRecordUpdatedBy")
302
+ createdExcusals StudentExcusal[] @relation("StudentExcusalCreatedBy")
303
+ revokedExcusals StudentExcusal[] @relation("StudentExcusalRevokedBy")
304
+ studentExcusals StudentExcusalStudent[]
305
+
221
306
  school School? @relation(fields: [schoolId], references: [id])
222
307
  schoolId String?
223
308
  schoolMemberships SchoolMembership[]
224
309
  sentSchoolInvites SchoolInvite[] @relation("SchoolInviteInvitedBy")
310
+ parentLinks ParentStudentLink[] @relation("ParentLinks")
311
+ studentParentLinks ParentStudentLink[] @relation("StudentParentLinks")
225
312
 
226
313
  departments Department[] @relation("UserDepartments")
227
314
  tags Tag[] @relation("UserTags")
@@ -243,6 +330,11 @@ model User {
243
330
  discussionReads DiscussionRead[]
244
331
  customGradeEntries CustomGradeEntry[] @relation("UserCustomGradeEntries")
245
332
  fileAnnotations FileAnnotation[] @relation("AuthoredFileAnnotations")
333
+ marketplaceListings MarketplaceListing[] @relation("MarketplaceListingAuthor")
334
+
335
+ feePlanEnrollments FeePlanEnrollment[]
336
+ feeInvoices FeeInvoice[]
337
+ feePaymentsRecorded FeePayment[] @relation("FeePaymentRecordedBy")
246
338
  }
247
339
 
248
340
  model SchoolMembership {
@@ -336,6 +428,10 @@ model Class {
336
428
  studentsCanCreateDiscussions Boolean @default(true)
337
429
  studentsCanReply Boolean @default(true)
338
430
 
431
+ /// Teacher-controlled ClassSidebar visibility. Keys match nav hrefs
432
+ /// without leading slash (e.g. "assignments", "quizzes"). Null = all defaults on.
433
+ sidebarFeatures Json?
434
+
339
435
  gradeCategories GradeCategory[]
340
436
  customGradeColumns CustomGradeColumn[]
341
437
 
@@ -407,6 +503,7 @@ model MarkScheme {
407
503
  structured String
408
504
  assignments Assignment[]
409
505
  tags Tag[] @relation("MarkSchemeTags")
506
+ marketplaceListings MarketplaceListing[]
410
507
 
411
508
  @@index([classId])
412
509
  @@index([departmentId])
@@ -509,12 +606,17 @@ model File {
509
606
  discussionReply DiscussionReply? @relation("DiscussionReplyFiles", fields: [discussionReplyId], references: [id], onDelete: Cascade)
510
607
  discussionReplyId String?
511
608
 
609
+ moduleItem ModuleItem? @relation("ModuleItemFiles", fields: [moduleItemId], references: [id], onDelete: Cascade)
610
+ moduleItemId String?
611
+
512
612
  schools School[] @relation("SchoolLogo")
613
+ schoolLetterheads School[] @relation("SchoolLetterhead")
513
614
 
514
615
  schoolDevelopementProgram SchoolDevelopementProgram? @relation("SchoolDevelopementProgramSupportingDocumentation", fields: [schoolDevelopementProgramId], references: [id], onDelete: Cascade)
515
616
  schoolDevelopementProgramId String?
516
617
 
517
618
  fileAnnotations FileAnnotation[] @relation("FileAnnotations")
619
+ marketplaceListings MarketplaceListing[]
518
620
  }
519
621
 
520
622
  enum FileAnnotationType {
@@ -585,6 +687,13 @@ model Assignment {
585
687
  gradeCategoryId String?
586
688
  worksheets Worksheet[] @relation("AssignmentWorksheets")
587
689
  targetedBySubmissions Submission[] @relation("SubmissionTargetedAssignment")
690
+
691
+ /// Optional timed window for QUIZ/TEST (minutes). Null = untimed.
692
+ timeLimitMinutes Int?
693
+ /// When true, students cannot unsubmit after submitting (typical for quizzes).
694
+ singleAttempt Boolean @default(false)
695
+ /// When false, file uploads and (re)submits after due date are rejected.
696
+ allowLateSubmissions Boolean @default(true)
588
697
  }
589
698
 
590
699
  model Announcement {
@@ -773,10 +882,35 @@ model Section {
773
882
  classId String
774
883
  class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
775
884
  assignments Assignment[]
885
+ moduleItems ModuleItem[]
776
886
  color String? @default("#3B82F6")
777
887
  order Int?
778
888
  }
779
889
 
890
+ /// Canvas-style module content: a rich page, a file collection, or a video item.
891
+ enum ModuleItemType {
892
+ PAGE
893
+ FILES
894
+ VIDEO
895
+ }
896
+
897
+ model ModuleItem {
898
+ id String @id @default(uuid())
899
+ sectionId String
900
+ section Section @relation(fields: [sectionId], references: [id], onDelete: Cascade)
901
+ title String
902
+ type ModuleItemType
903
+ /// Rich HTML/markdown body for PAGE items.
904
+ body String?
905
+ order Int?
906
+ published Boolean @default(true)
907
+ files File[] @relation("ModuleItemFiles")
908
+ createdAt DateTime @default(now())
909
+ updatedAt DateTime @updatedAt
910
+
911
+ @@index([sectionId])
912
+ }
913
+
780
914
  enum SessionType {
781
915
  AUTH
782
916
  EMAIL_VERIFICATION
@@ -811,6 +945,13 @@ model Event {
811
945
  attendance Attendance[]
812
946
  }
813
947
 
948
+ enum AttendanceStatus {
949
+ PRESENT
950
+ LATE
951
+ ABSENT
952
+ EXCUSED
953
+ }
954
+
814
955
  model Attendance {
815
956
  id String @id @default(uuid())
816
957
  date DateTime @default(now())
@@ -821,9 +962,68 @@ model Attendance {
821
962
  event Event? @relation(fields: [eventId], references: [id], onDelete: Cascade)
822
963
  eventId String?
823
964
 
824
- present User[] @relation("PresentAttendance")
825
- late User[] @relation("LateAttendance")
826
- absent User[] @relation("AbsentAttendance")
965
+ present User[] @relation("PresentAttendance")
966
+ late User[] @relation("LateAttendance")
967
+ absent User[] @relation("AbsentAttendance")
968
+ records AttendanceRecord[]
969
+ }
970
+
971
+ /// Per-student status for an attendance session (dual-written with legacy present/late/absent lists).
972
+ model AttendanceRecord {
973
+ id String @id @default(uuid())
974
+ attendanceId String
975
+ studentId String
976
+ status AttendanceStatus
977
+ note String?
978
+ excusedByExcusalId String?
979
+ updatedById String?
980
+ createdAt DateTime @default(now())
981
+ updatedAt DateTime @updatedAt
982
+
983
+ attendance Attendance @relation(fields: [attendanceId], references: [id], onDelete: Cascade)
984
+ student User @relation("AttendanceRecordStudent", fields: [studentId], references: [id], onDelete: Cascade)
985
+ excusedByExcusal StudentExcusal? @relation(fields: [excusedByExcusalId], references: [id], onDelete: SetNull)
986
+ updatedBy User? @relation("AttendanceRecordUpdatedBy", fields: [updatedById], references: [id], onDelete: SetNull)
987
+
988
+ @@unique([attendanceId, studentId])
989
+ @@index([studentId, status])
990
+ @@index([excusedByExcusalId])
991
+ @@index([attendanceId])
992
+ }
993
+
994
+ /// School-admin excusal covering one or more students for a date range.
995
+ model StudentExcusal {
996
+ id String @id @default(uuid())
997
+ schoolId String
998
+ startDate DateTime @db.Date
999
+ endDate DateTime @db.Date
1000
+ reason String
1001
+ note String?
1002
+ createdById String
1003
+ createdAt DateTime @default(now())
1004
+ updatedAt DateTime @updatedAt
1005
+ revokedAt DateTime?
1006
+ revokedById String?
1007
+
1008
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
1009
+ createdBy User @relation("StudentExcusalCreatedBy", fields: [createdById], references: [id], onDelete: Cascade)
1010
+ revokedBy User? @relation("StudentExcusalRevokedBy", fields: [revokedById], references: [id], onDelete: SetNull)
1011
+ students StudentExcusalStudent[]
1012
+ attendanceRecords AttendanceRecord[]
1013
+
1014
+ @@index([schoolId, startDate, endDate])
1015
+ @@index([schoolId, revokedAt])
1016
+ }
1017
+
1018
+ model StudentExcusalStudent {
1019
+ excusalId String
1020
+ studentId String
1021
+
1022
+ excusal StudentExcusal @relation(fields: [excusalId], references: [id], onDelete: Cascade)
1023
+ student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
1024
+
1025
+ @@id([excusalId, studentId])
1026
+ @@index([studentId])
827
1027
  }
828
1028
 
829
1029
  enum NotificationType {
@@ -834,6 +1034,10 @@ enum NotificationType {
834
1034
  DISCUSSION_MENTION
835
1035
  ANNOUNCEMENT
836
1036
  ATTENDANCE
1037
+ /// Fee due / paid / reminder events (A2 fees will emit these).
1038
+ FEES
1039
+ /// Excusal request/decision hooks (A1 owns StudentExcusal; emitters call helpers).
1040
+ EXCUSAL
837
1041
  SYSTEM
838
1042
  }
839
1043
 
@@ -987,6 +1191,7 @@ model Worksheet {
987
1191
 
988
1192
  status JobStatus? @default(NOT_STARTED)
989
1193
  tags ClassTag[] @relation("WorksheetToTag")
1194
+ marketplaceListings MarketplaceListing[]
990
1195
 
991
1196
  @@unique([classId, sourceUrl])
992
1197
  }
@@ -1005,6 +1210,13 @@ model WorksheetQuestion {
1005
1210
  createdAt DateTime @default(now())
1006
1211
  updatedAt DateTime @updatedAt
1007
1212
  studentResponses StudentQuestionProgress[]
1213
+
1214
+ /// Parent/child subquestions (e.g. multi-part worksheet items).
1215
+ parentQuestionId String?
1216
+ parentQuestion WorksheetQuestion? @relation("WorksheetSubquestions", fields: [parentQuestionId], references: [id], onDelete: Cascade)
1217
+ subquestions WorksheetQuestion[] @relation("WorksheetSubquestions")
1218
+
1219
+ @@index([parentQuestionId])
1008
1220
  }
1009
1221
 
1010
1222
  model StudentWorksheetResponse {
@@ -1180,6 +1392,47 @@ enum PermissionLevel {
1180
1392
  WRITE
1181
1393
  }
1182
1394
 
1395
+ /// User-created shareable content listing (markschemes, worksheets, files).
1396
+ /// Isolated Marketplace* tables — must not share schema with Attendance/Fee.
1397
+ /// Exercise/poll types are intentionally omitted from MarketplaceListingType for v1.
1398
+ model MarketplaceListing {
1399
+ id String @id @default(uuid())
1400
+ type MarketplaceListingType
1401
+ title String
1402
+ description String?
1403
+ authorId String
1404
+ author User @relation("MarketplaceListingAuthor", fields: [authorId], references: [id], onDelete: Cascade)
1405
+ schoolId String?
1406
+ school School? @relation(fields: [schoolId], references: [id], onDelete: SetNull)
1407
+ visibility MarketplaceVisibility @default(SCHOOL)
1408
+
1409
+ /// Live refs to the source resource (optional; source may later be deleted).
1410
+ markSchemeId String?
1411
+ markScheme MarkScheme? @relation(fields: [markSchemeId], references: [id], onDelete: SetNull)
1412
+ worksheetId String?
1413
+ worksheet Worksheet? @relation(fields: [worksheetId], references: [id], onDelete: SetNull)
1414
+ fileId String?
1415
+ file File? @relation(fields: [fileId], references: [id], onDelete: SetNull)
1416
+
1417
+ /// Snapshot captured at publish time so import survives source deletion.
1418
+ /// Shape varies by type — see marketplace service publish helpers.
1419
+ payload Json
1420
+
1421
+ tags String[] @default([])
1422
+
1423
+ createdAt DateTime @default(now())
1424
+ updatedAt DateTime @updatedAt
1425
+
1426
+ /// C10 moderation stubs — light reporting only; no full moderation queue yet.
1427
+ reportCount Int @default(0)
1428
+ isHidden Boolean @default(false)
1429
+
1430
+ @@index([type, visibility, createdAt])
1431
+ @@index([schoolId, visibility, createdAt])
1432
+ @@index([authorId])
1433
+ @@index([isHidden])
1434
+ }
1435
+
1183
1436
  /// Explicit ACL grant on a folder. Sits on top of (and is purely additive
1184
1437
  /// to) the scope-derived defaults computed from FolderScope. Files inherit
1185
1438
  /// from their parent folder — file-level overrides are deliberately deferred
@@ -1224,3 +1477,110 @@ model Permission {
1224
1477
  @@index([departmentId])
1225
1478
  @@index([schoolId])
1226
1479
  }
1480
+
1481
+ // ---------------------------------------------------------------------------
1482
+ // A2 — Monthly student fees + manual payment ledger (Nepal pilot)
1483
+ // Billing period = FeeInvoice.year + FeeInvoice.month (no separate term table;
1484
+ // AcademicYear/Term from other pilots can attach later without conflict).
1485
+ // ---------------------------------------------------------------------------
1486
+
1487
+ enum FeePaymentMethod {
1488
+ CASH
1489
+ BANK
1490
+ ESEWA_REF
1491
+ }
1492
+
1493
+ enum FeeInvoiceStatus {
1494
+ UNPAID
1495
+ PARTIAL
1496
+ PAID
1497
+ VOID
1498
+ }
1499
+
1500
+ /// Recurring monthly fee definition (e.g. "Tuition", "Transport").
1501
+ model FeePlan {
1502
+ id String @id @default(uuid())
1503
+ schoolId String
1504
+ name String
1505
+ description String?
1506
+ /// Whole NPR amount charged per billing month.
1507
+ amountNpr Int
1508
+ currency String @default("NPR")
1509
+ isActive Boolean @default(true)
1510
+ createdAt DateTime @default(now())
1511
+ updatedAt DateTime @updatedAt
1512
+
1513
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
1514
+ enrollments FeePlanEnrollment[]
1515
+ invoices FeeInvoice[]
1516
+
1517
+ @@index([schoolId])
1518
+ @@unique([schoolId, name])
1519
+ }
1520
+
1521
+ /// Students enrolled on a fee plan. Empty enrollments = all ACTIVE STUDENT members at invoice generation.
1522
+ model FeePlanEnrollment {
1523
+ id String @id @default(uuid())
1524
+ feePlanId String
1525
+ studentId String
1526
+ createdAt DateTime @default(now())
1527
+
1528
+ feePlan FeePlan @relation(fields: [feePlanId], references: [id], onDelete: Cascade)
1529
+ student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
1530
+
1531
+ @@unique([feePlanId, studentId])
1532
+ @@index([studentId])
1533
+ }
1534
+
1535
+ model FeeInvoice {
1536
+ id String @id @default(uuid())
1537
+ schoolId String
1538
+ feePlanId String
1539
+ studentId String
1540
+ /// Billing period (calendar month).
1541
+ year Int
1542
+ month Int
1543
+ /// Snapshot of plan amount at generation time.
1544
+ amountNpr Int
1545
+ currency String @default("NPR")
1546
+ status FeeInvoiceStatus @default(UNPAID)
1547
+ dueDate DateTime? @db.Date
1548
+ issuedAt DateTime @default(now())
1549
+ notes String?
1550
+ createdAt DateTime @default(now())
1551
+ updatedAt DateTime @updatedAt
1552
+
1553
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
1554
+ feePlan FeePlan @relation(fields: [feePlanId], references: [id], onDelete: Restrict)
1555
+ student User @relation(fields: [studentId], references: [id], onDelete: Cascade)
1556
+ payments FeePayment[]
1557
+
1558
+ @@unique([schoolId, studentId, feePlanId, year, month])
1559
+ @@index([schoolId, year, month])
1560
+ @@index([studentId])
1561
+ @@index([feePlanId])
1562
+ @@index([status])
1563
+ }
1564
+
1565
+ model FeePayment {
1566
+ id String @id @default(uuid())
1567
+ schoolId String
1568
+ invoiceId String
1569
+ amountNpr Int
1570
+ method FeePaymentMethod
1571
+ /// Required for BANK / ESEWA_REF (cheque no., eSewa txn id, etc.).
1572
+ reference String?
1573
+ paidAt DateTime @default(now())
1574
+ recordedById String
1575
+ notes String?
1576
+ createdAt DateTime @default(now())
1577
+ updatedAt DateTime @updatedAt
1578
+
1579
+ school School @relation(fields: [schoolId], references: [id], onDelete: Cascade)
1580
+ invoice FeeInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
1581
+ recordedBy User @relation("FeePaymentRecordedBy", fields: [recordedById], references: [id], onDelete: Restrict)
1582
+
1583
+ @@index([invoiceId])
1584
+ @@index([schoolId])
1585
+ @@index([paidAt])
1586
+ }