@studious-lms/server 1.22.0 → 1.24.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 +544 -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 +178 -0
  72. package/dist/models/marketplace.d.ts.map +1 -0
  73. package/dist/models/marketplace.js +115 -0
  74. package/dist/models/marketplace.js.map +1 -0
  75. package/dist/models/moduleItem.d.ts +304 -0
  76. package/dist/models/moduleItem.d.ts.map +1 -0
  77. package/dist/models/moduleItem.js +78 -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 +193 -0
  82. package/dist/models/school.d.ts.map +1 -1
  83. package/dist/models/school.js +80 -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 +11605 -7133
  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 +60 -0
  132. package/dist/routers/moduleItem.js.map +1 -0
  133. package/dist/routers/parent.d.ts +169 -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 +476 -6
  138. package/dist/routers/school.d.ts.map +1 -1
  139. package/dist/routers/school.js +130 -4
  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 +214 -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 +323 -10
  158. package/dist/services/attendance.d.ts.map +1 -1
  159. package/dist/services/attendance.js +385 -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 +27 -0
  174. package/dist/services/class.d.ts.map +1 -1
  175. package/dist/services/class.js +26 -3
  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 +310 -0
  182. package/dist/services/fees.js.map +1 -0
  183. package/dist/services/feesAuthz.d.ts +49 -0
  184. package/dist/services/feesAuthz.d.ts.map +1 -0
  185. package/dist/services/feesAuthz.js +52 -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 +628 -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 +188 -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 +133 -0
  210. package/dist/services/parentPortal.d.ts.map +1 -0
  211. package/dist/services/parentPortal.js +328 -0
  212. package/dist/services/parentPortal.js.map +1 -0
  213. package/dist/services/school.d.ts +122 -3
  214. package/dist/services/school.d.ts.map +1 -1
  215. package/dist/services/school.js +179 -47
  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 +236 -0
  220. package/dist/services/studentImport.js.map +1 -0
  221. package/dist/services/transcript.d.ts +61 -0
  222. package/dist/services/transcript.d.ts.map +1 -0
  223. package/dist/services/transcript.js +243 -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 +379 -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 +704 -73
  254. package/src/models/class.ts +1 -0
  255. package/src/models/fees.ts +349 -0
  256. package/src/models/marketplace.ts +158 -0
  257. package/src/models/moduleItem.ts +107 -0
  258. package/src/models/school.ts +95 -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 +89 -0
  268. package/src/routers/parent.ts +68 -0
  269. package/src/routers/school.ts +184 -1
  270. package/src/routers/worksheet.ts +11 -0
  271. package/src/services/academicCalendar.ts +299 -0
  272. package/src/services/assignment.ts +122 -12
  273. package/src/services/attendance.ts +548 -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 +25 -1
  278. package/src/services/fees.ts +424 -0
  279. package/src/services/feesAuthz.ts +77 -0
  280. package/src/services/gradebook.ts +70 -0
  281. package/src/services/idCard.ts +147 -0
  282. package/src/services/marketplace.ts +785 -0
  283. package/src/services/moduleItem.ts +274 -0
  284. package/src/services/notification.ts +43 -0
  285. package/src/services/parentPortal.ts +380 -0
  286. package/src/services/school.ts +243 -45
  287. package/src/services/studentImport.ts +273 -0
  288. package/src/services/transcript.ts +313 -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
@@ -190,6 +190,7 @@ const classIncludeForGet = {
190
190
  discussionsEnabled: true,
191
191
  studentsCanCreateDiscussions: true,
192
192
  studentsCanReply: true,
193
+ sidebarFeatures: true,
193
194
  } as const;
194
195
 
195
196
  const assignmentSelect = {
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Fee ledger model — FeePlan, FeeInvoice, FeePayment queries.
3
+ */
4
+ import {
5
+ FeeInvoiceStatus,
6
+ FeePaymentMethod,
7
+ Prisma,
8
+ } from "@prisma/client";
9
+ import { prisma } from "../lib/prisma.js";
10
+
11
+ const studentSelect = {
12
+ id: true,
13
+ username: true,
14
+ email: true,
15
+ profile: {
16
+ select: {
17
+ displayName: true,
18
+ profilePicture: true,
19
+ profilePictureThumbnail: true,
20
+ },
21
+ },
22
+ } as const;
23
+
24
+ const planSelect = {
25
+ id: true,
26
+ schoolId: true,
27
+ name: true,
28
+ description: true,
29
+ amountNpr: true,
30
+ currency: true,
31
+ isActive: true,
32
+ createdAt: true,
33
+ updatedAt: true,
34
+ _count: { select: { enrollments: true, invoices: true } },
35
+ } as const;
36
+
37
+ const invoiceSelect = {
38
+ id: true,
39
+ schoolId: true,
40
+ feePlanId: true,
41
+ studentId: true,
42
+ year: true,
43
+ month: true,
44
+ amountNpr: true,
45
+ currency: true,
46
+ status: true,
47
+ dueDate: true,
48
+ issuedAt: true,
49
+ notes: true,
50
+ createdAt: true,
51
+ updatedAt: true,
52
+ feePlan: { select: { id: true, name: true } },
53
+ student: { select: studentSelect },
54
+ payments: {
55
+ orderBy: { paidAt: "desc" as const },
56
+ select: {
57
+ id: true,
58
+ amountNpr: true,
59
+ method: true,
60
+ reference: true,
61
+ paidAt: true,
62
+ notes: true,
63
+ recordedById: true,
64
+ },
65
+ },
66
+ } as const;
67
+
68
+ export function listFeePlans(schoolId: string) {
69
+ return prisma.feePlan.findMany({
70
+ where: { schoolId },
71
+ orderBy: [{ isActive: "desc" }, { name: "asc" }],
72
+ select: planSelect,
73
+ });
74
+ }
75
+
76
+ export function getFeePlan(planId: string) {
77
+ return prisma.feePlan.findUnique({
78
+ where: { id: planId },
79
+ select: {
80
+ ...planSelect,
81
+ enrollments: {
82
+ select: {
83
+ id: true,
84
+ studentId: true,
85
+ student: { select: studentSelect },
86
+ },
87
+ },
88
+ },
89
+ });
90
+ }
91
+
92
+ export function createFeePlan(data: {
93
+ schoolId: string;
94
+ name: string;
95
+ description?: string | null;
96
+ amountNpr: number;
97
+ currency?: string;
98
+ isActive?: boolean;
99
+ }) {
100
+ return prisma.feePlan.create({
101
+ data: {
102
+ schoolId: data.schoolId,
103
+ name: data.name,
104
+ description: data.description,
105
+ amountNpr: data.amountNpr,
106
+ currency: data.currency ?? "NPR",
107
+ isActive: data.isActive ?? true,
108
+ },
109
+ select: planSelect,
110
+ });
111
+ }
112
+
113
+ export function updateFeePlan(
114
+ planId: string,
115
+ data: {
116
+ name?: string;
117
+ description?: string | null;
118
+ amountNpr?: number;
119
+ isActive?: boolean;
120
+ }
121
+ ) {
122
+ return prisma.feePlan.update({
123
+ where: { id: planId },
124
+ data,
125
+ select: planSelect,
126
+ });
127
+ }
128
+
129
+ export function setFeePlanEnrollments(planId: string, studentIds: string[]) {
130
+ return prisma.$transaction(async (tx) => {
131
+ await tx.feePlanEnrollment.deleteMany({ where: { feePlanId: planId } });
132
+ if (studentIds.length) {
133
+ await tx.feePlanEnrollment.createMany({
134
+ data: studentIds.map((studentId) => ({ feePlanId: planId, studentId })),
135
+ skipDuplicates: true,
136
+ });
137
+ }
138
+ return tx.feePlan.findUnique({
139
+ where: { id: planId },
140
+ select: {
141
+ ...planSelect,
142
+ enrollments: {
143
+ select: {
144
+ id: true,
145
+ studentId: true,
146
+ student: { select: studentSelect },
147
+ },
148
+ },
149
+ },
150
+ });
151
+ });
152
+ }
153
+
154
+ export function listActiveStudentIds(schoolId: string) {
155
+ return prisma.schoolMembership
156
+ .findMany({
157
+ where: { schoolId, role: "STUDENT", status: "ACTIVE" },
158
+ select: { userId: true },
159
+ })
160
+ .then((rows) => rows.map((r) => r.userId));
161
+ }
162
+
163
+ export function listPlanEnrollmentIds(planId: string) {
164
+ return prisma.feePlanEnrollment
165
+ .findMany({
166
+ where: { feePlanId: planId },
167
+ select: { studentId: true },
168
+ })
169
+ .then((rows) => rows.map((r) => r.studentId));
170
+ }
171
+
172
+ export function createFeeInvoices(
173
+ rows: {
174
+ schoolId: string;
175
+ feePlanId: string;
176
+ studentId: string;
177
+ year: number;
178
+ month: number;
179
+ amountNpr: number;
180
+ currency: string;
181
+ dueDate?: Date | null;
182
+ }[]
183
+ ) {
184
+ return prisma.feeInvoice.createMany({
185
+ data: rows,
186
+ skipDuplicates: true,
187
+ });
188
+ }
189
+
190
+ export function listFeeInvoices(filters: {
191
+ schoolId: string;
192
+ year?: number;
193
+ month?: number;
194
+ studentId?: string;
195
+ feePlanId?: string;
196
+ status?: FeeInvoiceStatus;
197
+ }) {
198
+ const where: Prisma.FeeInvoiceWhereInput = {
199
+ schoolId: filters.schoolId,
200
+ };
201
+ if (filters.year != null) where.year = filters.year;
202
+ if (filters.month != null) where.month = filters.month;
203
+ if (filters.studentId) where.studentId = filters.studentId;
204
+ if (filters.feePlanId) where.feePlanId = filters.feePlanId;
205
+ if (filters.status) where.status = filters.status;
206
+
207
+ return prisma.feeInvoice.findMany({
208
+ where,
209
+ orderBy: [{ year: "desc" }, { month: "desc" }, { issuedAt: "desc" }],
210
+ select: invoiceSelect,
211
+ });
212
+ }
213
+
214
+ export function getFeeInvoice(invoiceId: string) {
215
+ return prisma.feeInvoice.findUnique({
216
+ where: { id: invoiceId },
217
+ select: {
218
+ ...invoiceSelect,
219
+ school: {
220
+ select: {
221
+ id: true,
222
+ name: true,
223
+ letterheadFileId: true,
224
+ letterhead: {
225
+ select: { id: true, path: true, type: true, name: true },
226
+ },
227
+ },
228
+ },
229
+ },
230
+ });
231
+ }
232
+
233
+ export function createFeePayment(data: {
234
+ schoolId: string;
235
+ invoiceId: string;
236
+ amountNpr: number;
237
+ method: FeePaymentMethod;
238
+ reference?: string | null;
239
+ paidAt?: Date;
240
+ recordedById: string;
241
+ notes?: string | null;
242
+ }) {
243
+ return prisma.feePayment.create({
244
+ data: {
245
+ schoolId: data.schoolId,
246
+ invoiceId: data.invoiceId,
247
+ amountNpr: data.amountNpr,
248
+ method: data.method,
249
+ reference: data.reference,
250
+ paidAt: data.paidAt,
251
+ recordedById: data.recordedById,
252
+ notes: data.notes,
253
+ },
254
+ });
255
+ }
256
+
257
+ export function sumPaymentsForInvoice(invoiceId: string) {
258
+ return prisma.feePayment.aggregate({
259
+ where: { invoiceId },
260
+ _sum: { amountNpr: true },
261
+ });
262
+ }
263
+
264
+ export function updateInvoiceStatus(invoiceId: string, status: FeeInvoiceStatus) {
265
+ return prisma.feeInvoice.update({
266
+ where: { id: invoiceId },
267
+ data: { status },
268
+ });
269
+ }
270
+
271
+ export function getFeePayment(paymentId: string) {
272
+ return prisma.feePayment.findUnique({
273
+ where: { id: paymentId },
274
+ include: {
275
+ invoice: {
276
+ select: {
277
+ ...invoiceSelect,
278
+ school: {
279
+ select: {
280
+ id: true,
281
+ name: true,
282
+ letterheadFileId: true,
283
+ letterhead: {
284
+ select: { id: true, path: true, type: true, name: true },
285
+ },
286
+ },
287
+ },
288
+ },
289
+ },
290
+ recordedBy: {
291
+ select: {
292
+ id: true,
293
+ username: true,
294
+ profile: { select: { displayName: true } },
295
+ },
296
+ },
297
+ },
298
+ });
299
+ }
300
+
301
+ export async function listStudentBalances(schoolId: string) {
302
+ const invoices = await prisma.feeInvoice.findMany({
303
+ where: {
304
+ schoolId,
305
+ status: { in: ["UNPAID", "PARTIAL"] },
306
+ },
307
+ select: {
308
+ studentId: true,
309
+ amountNpr: true,
310
+ student: { select: studentSelect },
311
+ payments: { select: { amountNpr: true } },
312
+ },
313
+ });
314
+
315
+ const byStudent = new Map<
316
+ string,
317
+ {
318
+ student: (typeof invoices)[number]["student"];
319
+ invoicedNpr: number;
320
+ paidNpr: number;
321
+ balanceNpr: number;
322
+ openInvoiceCount: number;
323
+ }
324
+ >();
325
+
326
+ for (const inv of invoices) {
327
+ const paid = inv.payments.reduce((s, p) => s + p.amountNpr, 0);
328
+ const balance = Math.max(0, inv.amountNpr - paid);
329
+ const existing = byStudent.get(inv.studentId);
330
+ if (existing) {
331
+ existing.invoicedNpr += inv.amountNpr;
332
+ existing.paidNpr += paid;
333
+ existing.balanceNpr += balance;
334
+ existing.openInvoiceCount += 1;
335
+ } else {
336
+ byStudent.set(inv.studentId, {
337
+ student: inv.student,
338
+ invoicedNpr: inv.amountNpr,
339
+ paidNpr: paid,
340
+ balanceNpr: balance,
341
+ openInvoiceCount: 1,
342
+ });
343
+ }
344
+ }
345
+
346
+ return Array.from(byStudent.entries())
347
+ .map(([studentId, row]) => ({ studentId, ...row }))
348
+ .sort((a, b) => b.balanceNpr - a.balanceNpr);
349
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * MarketplaceListing data access — B1 v1 (markschemes, worksheets, files).
3
+ * Isolated Marketplace* tables only; no Attendance/Fee coupling.
4
+ */
5
+ import type {
6
+ MarketplaceListingType,
7
+ MarketplaceVisibility,
8
+ Prisma,
9
+ } from "@prisma/client";
10
+ import { prisma } from "../lib/prisma.js";
11
+
12
+ export const marketplaceListingInclude = {
13
+ author: {
14
+ select: {
15
+ id: true,
16
+ username: true,
17
+ profile: { select: { displayName: true } },
18
+ },
19
+ },
20
+ school: { select: { id: true, name: true } },
21
+ } as const;
22
+
23
+ export type MarketplaceListingWithRelations = Prisma.MarketplaceListingGetPayload<{
24
+ include: typeof marketplaceListingInclude;
25
+ }>;
26
+
27
+ export async function createMarketplaceListing(data: {
28
+ type: MarketplaceListingType;
29
+ title: string;
30
+ description?: string | null;
31
+ authorId: string;
32
+ schoolId?: string | null;
33
+ visibility: MarketplaceVisibility;
34
+ markSchemeId?: string | null;
35
+ worksheetId?: string | null;
36
+ fileId?: string | null;
37
+ payload: Prisma.InputJsonValue;
38
+ tags?: string[];
39
+ }) {
40
+ return prisma.marketplaceListing.create({
41
+ data: {
42
+ type: data.type,
43
+ title: data.title,
44
+ description: data.description ?? null,
45
+ authorId: data.authorId,
46
+ schoolId: data.schoolId ?? null,
47
+ visibility: data.visibility,
48
+ markSchemeId: data.markSchemeId ?? null,
49
+ worksheetId: data.worksheetId ?? null,
50
+ fileId: data.fileId ?? null,
51
+ payload: data.payload,
52
+ tags: data.tags ?? [],
53
+ },
54
+ include: marketplaceListingInclude,
55
+ });
56
+ }
57
+
58
+ export async function findMarketplaceListingById(id: string) {
59
+ return prisma.marketplaceListing.findUnique({
60
+ where: { id },
61
+ include: marketplaceListingInclude,
62
+ });
63
+ }
64
+
65
+ export async function searchMarketplaceListings(input: {
66
+ schoolIds: string[];
67
+ type?: MarketplaceListingType;
68
+ search?: string;
69
+ tags?: string[];
70
+ cursor?: string;
71
+ limit: number;
72
+ /** When true, include the caller's own hidden listings (for authors). */
73
+ authorId?: string;
74
+ }) {
75
+ const search = input.search?.trim();
76
+ const where: Prisma.MarketplaceListingWhereInput = {
77
+ AND: [
78
+ {
79
+ OR: [
80
+ { visibility: "PUBLIC", isHidden: false },
81
+ {
82
+ visibility: "SCHOOL",
83
+ isHidden: false,
84
+ schoolId: { in: input.schoolIds },
85
+ },
86
+ // Authors can always see their own listings (including hidden).
87
+ ...(input.authorId ? [{ authorId: input.authorId }] : []),
88
+ ],
89
+ },
90
+ ...(input.type ? [{ type: input.type }] : []),
91
+ ...(input.tags?.length
92
+ ? [{ tags: { hasSome: input.tags } }]
93
+ : []),
94
+ ...(search
95
+ ? [
96
+ {
97
+ OR: [
98
+ { title: { contains: search, mode: "insensitive" as const } },
99
+ {
100
+ description: {
101
+ contains: search,
102
+ mode: "insensitive" as const,
103
+ },
104
+ },
105
+ { tags: { has: search } },
106
+ ],
107
+ },
108
+ ]
109
+ : []),
110
+ ],
111
+ };
112
+
113
+ const rows = await prisma.marketplaceListing.findMany({
114
+ where,
115
+ include: marketplaceListingInclude,
116
+ orderBy: [{ createdAt: "desc" }, { id: "desc" }],
117
+ take: input.limit + 1,
118
+ ...(input.cursor
119
+ ? {
120
+ cursor: { id: input.cursor },
121
+ skip: 1,
122
+ }
123
+ : {}),
124
+ });
125
+
126
+ let nextCursor: string | undefined;
127
+ if (rows.length > input.limit) {
128
+ const next = rows.pop();
129
+ nextCursor = next?.id;
130
+ }
131
+
132
+ return { items: rows, nextCursor };
133
+ }
134
+
135
+ export async function incrementMarketplaceReportCount(id: string) {
136
+ return prisma.marketplaceListing.update({
137
+ where: { id },
138
+ data: { reportCount: { increment: 1 } },
139
+ select: { id: true, reportCount: true, isHidden: true },
140
+ });
141
+ }
142
+
143
+ export async function setMarketplaceListingHidden(
144
+ id: string,
145
+ isHidden: boolean
146
+ ) {
147
+ return prisma.marketplaceListing.update({
148
+ where: { id },
149
+ data: { isHidden },
150
+ include: marketplaceListingInclude,
151
+ });
152
+ }
153
+
154
+ export async function deleteMarketplaceListing(id: string, authorId: string) {
155
+ return prisma.marketplaceListing.deleteMany({
156
+ where: { id, authorId },
157
+ });
158
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * ModuleItem model – Canvas-style pages / file collections / videos in a section.
3
+ */
4
+ import { prisma } from "../lib/prisma.js";
5
+ import type { ModuleItemType } from "@prisma/client";
6
+ import {
7
+ classMemberOrSchoolAdminWhere,
8
+ classTeacherOrSchoolAdminWhere,
9
+ } from "./access.js";
10
+
11
+ const moduleItemInclude = {
12
+ files: {
13
+ select: {
14
+ id: true,
15
+ name: true,
16
+ type: true,
17
+ size: true,
18
+ path: true,
19
+ uploadStatus: true,
20
+ },
21
+ },
22
+ section: {
23
+ select: { id: true, name: true, classId: true, color: true },
24
+ },
25
+ } as const;
26
+
27
+ export function findModuleItemsByClassId(classId: string) {
28
+ return prisma.moduleItem.findMany({
29
+ where: { section: { classId } },
30
+ include: moduleItemInclude,
31
+ orderBy: [{ section: { order: "asc" } }, { order: "asc" }],
32
+ });
33
+ }
34
+
35
+ export function findModuleItemsBySectionId(sectionId: string) {
36
+ return prisma.moduleItem.findMany({
37
+ where: { sectionId },
38
+ include: moduleItemInclude,
39
+ orderBy: { order: "asc" },
40
+ });
41
+ }
42
+
43
+ export function findModuleItemByIdForMember(id: string, userId: string) {
44
+ return prisma.moduleItem.findFirst({
45
+ where: {
46
+ id,
47
+ section: { class: classMemberOrSchoolAdminWhere(userId) },
48
+ },
49
+ include: moduleItemInclude,
50
+ });
51
+ }
52
+
53
+ export function findModuleItemByIdForTeacher(id: string, userId: string) {
54
+ return prisma.moduleItem.findFirst({
55
+ where: {
56
+ id,
57
+ section: { class: classTeacherOrSchoolAdminWhere(userId) },
58
+ },
59
+ include: moduleItemInclude,
60
+ });
61
+ }
62
+
63
+ export function createModuleItem(data: {
64
+ sectionId: string;
65
+ title: string;
66
+ type: ModuleItemType;
67
+ body?: string | null;
68
+ order?: number | null;
69
+ published?: boolean;
70
+ }) {
71
+ return prisma.moduleItem.create({
72
+ data,
73
+ include: moduleItemInclude,
74
+ });
75
+ }
76
+
77
+ export function updateModuleItem(
78
+ id: string,
79
+ data: {
80
+ title?: string;
81
+ body?: string | null;
82
+ order?: number | null;
83
+ published?: boolean;
84
+ type?: ModuleItemType;
85
+ sectionId?: string;
86
+ }
87
+ ) {
88
+ return prisma.moduleItem.update({
89
+ where: { id },
90
+ data,
91
+ include: moduleItemInclude,
92
+ });
93
+ }
94
+
95
+ export function deleteModuleItem(id: string) {
96
+ return prisma.moduleItem.delete({ where: { id } });
97
+ }
98
+
99
+ export function attachFilesToModuleItem(
100
+ moduleItemId: string,
101
+ fileIds: string[]
102
+ ) {
103
+ return prisma.file.updateMany({
104
+ where: { id: { in: fileIds } },
105
+ data: { moduleItemId },
106
+ });
107
+ }