@studious-lms/server 1.23.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.
- 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 +544 -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 +178 -0
- package/dist/models/marketplace.d.ts.map +1 -0
- package/dist/models/marketplace.js +115 -0
- package/dist/models/marketplace.js.map +1 -0
- package/dist/models/moduleItem.d.ts +304 -0
- package/dist/models/moduleItem.d.ts.map +1 -0
- package/dist/models/moduleItem.js +78 -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 +11341 -7097
- 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 +60 -0
- package/dist/routers/moduleItem.js.map +1 -0
- package/dist/routers/parent.d.ts +169 -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 +100 -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 +214 -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 +323 -10
- package/dist/services/attendance.d.ts.map +1 -1
- package/dist/services/attendance.js +385 -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 +27 -0
- package/dist/services/class.d.ts.map +1 -1
- package/dist/services/class.js +26 -3
- 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 +310 -0
- package/dist/services/fees.js.map +1 -0
- package/dist/services/feesAuthz.d.ts +49 -0
- package/dist/services/feesAuthz.d.ts.map +1 -0
- package/dist/services/feesAuthz.js +52 -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 +628 -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 +188 -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 +133 -0
- package/dist/services/parentPortal.d.ts.map +1 -0
- package/dist/services/parentPortal.js +328 -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 +65 -45
- 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 +236 -0
- package/dist/services/studentImport.js.map +1 -0
- package/dist/services/transcript.d.ts +61 -0
- package/dist/services/transcript.d.ts.map +1 -0
- package/dist/services/transcript.js +243 -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 +379 -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 +704 -73
- package/src/models/class.ts +1 -0
- package/src/models/fees.ts +349 -0
- package/src/models/marketplace.ts +158 -0
- package/src/models/moduleItem.ts +107 -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 +89 -0
- package/src/routers/parent.ts +68 -0
- package/src/routers/school.ts +139 -1
- package/src/routers/worksheet.ts +11 -0
- package/src/services/academicCalendar.ts +299 -0
- package/src/services/assignment.ts +122 -12
- package/src/services/attendance.ts +548 -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 +25 -1
- package/src/services/fees.ts +424 -0
- package/src/services/feesAuthz.ts +77 -0
- package/src/services/gradebook.ts +70 -0
- package/src/services/idCard.ts +147 -0
- package/src/services/marketplace.ts +785 -0
- package/src/services/moduleItem.ts +274 -0
- package/src/services/notification.ts +43 -0
- package/src/services/parentPortal.ts +380 -0
- package/src/services/school.ts +65 -44
- package/src/services/studentImport.ts +273 -0
- package/src/services/transcript.ts +313 -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,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fee ledger service — school-admin gated monthly invoicing + payment recording.
|
|
3
|
+
* Payment gateway intentionally deferred; this is a manual ledger.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f88adefd-f266-510d-92db-2aeb2cb27715")}catch(e){}}();
|
|
7
|
+
import { TRPCError } from "@trpc/server";
|
|
8
|
+
import { findActiveSchoolAdminMembership } from "../models/school.js";
|
|
9
|
+
import { createFeeInvoices, createFeePayment, createFeePlan, getFeeInvoice, getFeePayment, getFeePlan, listActiveStudentIds, listFeeInvoices, listFeePlans, listPlanEnrollmentIds, listStudentBalances, setFeePlanEnrollments, sumPaymentsForInvoice, updateFeePlan, updateInvoiceStatus, } from "../models/fees.js";
|
|
10
|
+
import { buildFeePdf, formatNpr, monthLabel, } from "../lib/feePdf.js";
|
|
11
|
+
async function requireAdmin(schoolId, userId) {
|
|
12
|
+
const membership = await findActiveSchoolAdminMembership(schoolId, userId);
|
|
13
|
+
if (!membership) {
|
|
14
|
+
throw new TRPCError({
|
|
15
|
+
code: "FORBIDDEN",
|
|
16
|
+
message: "Only school admins can manage fees",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return membership;
|
|
20
|
+
}
|
|
21
|
+
function assertMonth(month) {
|
|
22
|
+
if (month < 1 || month > 12) {
|
|
23
|
+
throw new TRPCError({
|
|
24
|
+
code: "BAD_REQUEST",
|
|
25
|
+
message: "month must be between 1 and 12",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function invoicePaidTotal(payments) {
|
|
30
|
+
return payments.reduce((s, p) => s + p.amountNpr, 0);
|
|
31
|
+
}
|
|
32
|
+
function deriveStatus(amountNpr, paidNpr, current) {
|
|
33
|
+
if (current === "VOID")
|
|
34
|
+
return "VOID";
|
|
35
|
+
if (paidNpr <= 0)
|
|
36
|
+
return "UNPAID";
|
|
37
|
+
if (paidNpr >= amountNpr)
|
|
38
|
+
return "PAID";
|
|
39
|
+
return "PARTIAL";
|
|
40
|
+
}
|
|
41
|
+
function withBalance(invoice) {
|
|
42
|
+
const paidNpr = invoicePaidTotal(invoice.payments);
|
|
43
|
+
return {
|
|
44
|
+
...invoice,
|
|
45
|
+
paidNpr,
|
|
46
|
+
balanceNpr: Math.max(0, invoice.amountNpr - paidNpr),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export async function listPlans(userId, schoolId) {
|
|
50
|
+
await requireAdmin(schoolId, userId);
|
|
51
|
+
const plans = await listFeePlans(schoolId);
|
|
52
|
+
return { plans };
|
|
53
|
+
}
|
|
54
|
+
export async function getPlan(userId, planId) {
|
|
55
|
+
const plan = await getFeePlan(planId);
|
|
56
|
+
if (!plan) {
|
|
57
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Fee plan not found" });
|
|
58
|
+
}
|
|
59
|
+
await requireAdmin(plan.schoolId, userId);
|
|
60
|
+
return { plan };
|
|
61
|
+
}
|
|
62
|
+
export async function createPlan(userId, input) {
|
|
63
|
+
await requireAdmin(input.schoolId, userId);
|
|
64
|
+
if (input.amountNpr <= 0) {
|
|
65
|
+
throw new TRPCError({
|
|
66
|
+
code: "BAD_REQUEST",
|
|
67
|
+
message: "amountNpr must be positive",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const plan = await createFeePlan({
|
|
71
|
+
schoolId: input.schoolId,
|
|
72
|
+
name: input.name.trim(),
|
|
73
|
+
description: input.description?.trim() || null,
|
|
74
|
+
amountNpr: input.amountNpr,
|
|
75
|
+
});
|
|
76
|
+
if (input.studentIds?.length) {
|
|
77
|
+
const updated = await setFeePlanEnrollments(plan.id, input.studentIds);
|
|
78
|
+
return { plan: updated };
|
|
79
|
+
}
|
|
80
|
+
return { plan };
|
|
81
|
+
}
|
|
82
|
+
export async function updatePlan(userId, input) {
|
|
83
|
+
const existing = await getFeePlan(input.planId);
|
|
84
|
+
if (!existing) {
|
|
85
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Fee plan not found" });
|
|
86
|
+
}
|
|
87
|
+
await requireAdmin(existing.schoolId, userId);
|
|
88
|
+
if (input.amountNpr != null && input.amountNpr <= 0) {
|
|
89
|
+
throw new TRPCError({
|
|
90
|
+
code: "BAD_REQUEST",
|
|
91
|
+
message: "amountNpr must be positive",
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const plan = await updateFeePlan(input.planId, {
|
|
95
|
+
name: input.name?.trim(),
|
|
96
|
+
description: input.description,
|
|
97
|
+
amountNpr: input.amountNpr,
|
|
98
|
+
isActive: input.isActive,
|
|
99
|
+
});
|
|
100
|
+
return { plan };
|
|
101
|
+
}
|
|
102
|
+
export async function setPlanStudents(userId, input) {
|
|
103
|
+
const existing = await getFeePlan(input.planId);
|
|
104
|
+
if (!existing) {
|
|
105
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Fee plan not found" });
|
|
106
|
+
}
|
|
107
|
+
await requireAdmin(existing.schoolId, userId);
|
|
108
|
+
const plan = await setFeePlanEnrollments(input.planId, input.studentIds);
|
|
109
|
+
return { plan };
|
|
110
|
+
}
|
|
111
|
+
export async function generateMonthlyInvoices(userId, input) {
|
|
112
|
+
await requireAdmin(input.schoolId, userId);
|
|
113
|
+
assertMonth(input.month);
|
|
114
|
+
const plan = await getFeePlan(input.feePlanId);
|
|
115
|
+
if (!plan || plan.schoolId !== input.schoolId) {
|
|
116
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Fee plan not found" });
|
|
117
|
+
}
|
|
118
|
+
if (!plan.isActive) {
|
|
119
|
+
throw new TRPCError({
|
|
120
|
+
code: "BAD_REQUEST",
|
|
121
|
+
message: "Cannot generate invoices for an inactive fee plan",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
let studentIds = input.studentIds;
|
|
125
|
+
if (!studentIds?.length) {
|
|
126
|
+
const enrolled = await listPlanEnrollmentIds(plan.id);
|
|
127
|
+
studentIds = enrolled.length
|
|
128
|
+
? enrolled
|
|
129
|
+
: await listActiveStudentIds(input.schoolId);
|
|
130
|
+
}
|
|
131
|
+
if (!studentIds.length) {
|
|
132
|
+
throw new TRPCError({
|
|
133
|
+
code: "BAD_REQUEST",
|
|
134
|
+
message: "No students to invoice",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const result = await createFeeInvoices(studentIds.map((studentId) => ({
|
|
138
|
+
schoolId: input.schoolId,
|
|
139
|
+
feePlanId: plan.id,
|
|
140
|
+
studentId,
|
|
141
|
+
year: input.year,
|
|
142
|
+
month: input.month,
|
|
143
|
+
amountNpr: plan.amountNpr,
|
|
144
|
+
currency: plan.currency,
|
|
145
|
+
dueDate: input.dueDate ?? null,
|
|
146
|
+
})));
|
|
147
|
+
const invoices = await listFeeInvoices({
|
|
148
|
+
schoolId: input.schoolId,
|
|
149
|
+
feePlanId: plan.id,
|
|
150
|
+
year: input.year,
|
|
151
|
+
month: input.month,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
createdCount: result.count,
|
|
155
|
+
invoices: invoices.map(withBalance),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export async function listInvoices(userId, input) {
|
|
159
|
+
await requireAdmin(input.schoolId, userId);
|
|
160
|
+
if (input.month != null)
|
|
161
|
+
assertMonth(input.month);
|
|
162
|
+
const invoices = await listFeeInvoices(input);
|
|
163
|
+
return { invoices: invoices.map(withBalance) };
|
|
164
|
+
}
|
|
165
|
+
export async function getInvoice(userId, invoiceId) {
|
|
166
|
+
const invoice = await getFeeInvoice(invoiceId);
|
|
167
|
+
if (!invoice) {
|
|
168
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invoice not found" });
|
|
169
|
+
}
|
|
170
|
+
await requireAdmin(invoice.schoolId, userId);
|
|
171
|
+
return { invoice: withBalance(invoice) };
|
|
172
|
+
}
|
|
173
|
+
export async function recordPayment(userId, input) {
|
|
174
|
+
const invoice = await getFeeInvoice(input.invoiceId);
|
|
175
|
+
if (!invoice) {
|
|
176
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invoice not found" });
|
|
177
|
+
}
|
|
178
|
+
await requireAdmin(invoice.schoolId, userId);
|
|
179
|
+
if (invoice.status === "VOID") {
|
|
180
|
+
throw new TRPCError({
|
|
181
|
+
code: "BAD_REQUEST",
|
|
182
|
+
message: "Cannot record payment on a void invoice",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (input.amountNpr <= 0) {
|
|
186
|
+
throw new TRPCError({
|
|
187
|
+
code: "BAD_REQUEST",
|
|
188
|
+
message: "amountNpr must be positive",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if ((input.method === "BANK" || input.method === "ESEWA_REF") &&
|
|
192
|
+
!input.reference?.trim()) {
|
|
193
|
+
throw new TRPCError({
|
|
194
|
+
code: "BAD_REQUEST",
|
|
195
|
+
message: "reference is required for BANK and ESEWA_REF payments",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const paidSoFar = invoicePaidTotal(invoice.payments);
|
|
199
|
+
const balance = Math.max(0, invoice.amountNpr - paidSoFar);
|
|
200
|
+
if (input.amountNpr > balance) {
|
|
201
|
+
throw new TRPCError({
|
|
202
|
+
code: "BAD_REQUEST",
|
|
203
|
+
message: `Payment exceeds balance (${formatNpr(balance)})`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const payment = await createFeePayment({
|
|
207
|
+
schoolId: invoice.schoolId,
|
|
208
|
+
invoiceId: invoice.id,
|
|
209
|
+
amountNpr: input.amountNpr,
|
|
210
|
+
method: input.method,
|
|
211
|
+
reference: input.reference?.trim() || null,
|
|
212
|
+
paidAt: input.paidAt,
|
|
213
|
+
recordedById: userId,
|
|
214
|
+
notes: input.notes?.trim() || null,
|
|
215
|
+
});
|
|
216
|
+
const aggregate = await sumPaymentsForInvoice(invoice.id);
|
|
217
|
+
const newPaid = aggregate._sum.amountNpr ?? 0;
|
|
218
|
+
const status = deriveStatus(invoice.amountNpr, newPaid, invoice.status);
|
|
219
|
+
await updateInvoiceStatus(invoice.id, status);
|
|
220
|
+
const refreshed = await getFeeInvoice(invoice.id);
|
|
221
|
+
return {
|
|
222
|
+
payment,
|
|
223
|
+
invoice: refreshed ? withBalance(refreshed) : null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
export async function listBalances(userId, schoolId) {
|
|
227
|
+
await requireAdmin(schoolId, userId);
|
|
228
|
+
const balances = await listStudentBalances(schoolId);
|
|
229
|
+
return { balances };
|
|
230
|
+
}
|
|
231
|
+
export async function getInvoicePdf(userId, invoiceId) {
|
|
232
|
+
const invoice = await getFeeInvoice(invoiceId);
|
|
233
|
+
if (!invoice) {
|
|
234
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Invoice not found" });
|
|
235
|
+
}
|
|
236
|
+
await requireAdmin(invoice.schoolId, userId);
|
|
237
|
+
const paidNpr = invoicePaidTotal(invoice.payments);
|
|
238
|
+
const balanceNpr = Math.max(0, invoice.amountNpr - paidNpr);
|
|
239
|
+
const studentName = invoice.student.profile?.displayName || invoice.student.username;
|
|
240
|
+
const bytes = await buildFeePdf({
|
|
241
|
+
schoolName: invoice.school.name,
|
|
242
|
+
title: "Fee Invoice",
|
|
243
|
+
subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,
|
|
244
|
+
letterhead: invoice.school.letterhead
|
|
245
|
+
? {
|
|
246
|
+
path: invoice.school.letterhead.path,
|
|
247
|
+
type: invoice.school.letterhead.type,
|
|
248
|
+
}
|
|
249
|
+
: null,
|
|
250
|
+
lines: [
|
|
251
|
+
{ label: "Invoice ID", value: invoice.id.slice(0, 8).toUpperCase() },
|
|
252
|
+
{ label: "Student", value: studentName },
|
|
253
|
+
{ label: "Period", value: monthLabel(invoice.year, invoice.month) },
|
|
254
|
+
{ label: "Plan", value: invoice.feePlan.name },
|
|
255
|
+
{ label: "Amount", value: formatNpr(invoice.amountNpr) },
|
|
256
|
+
{ label: "Paid", value: formatNpr(paidNpr) },
|
|
257
|
+
{ label: "Balance", value: formatNpr(balanceNpr) },
|
|
258
|
+
{ label: "Status", value: invoice.status },
|
|
259
|
+
],
|
|
260
|
+
footerNote: "Manual ledger — not a gateway receipt. Keep for school records.",
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
fileName: `invoice-${invoice.year}-${String(invoice.month).padStart(2, "0")}-${invoice.id.slice(0, 8)}.pdf`,
|
|
264
|
+
contentType: "application/pdf",
|
|
265
|
+
base64: Buffer.from(bytes).toString("base64"),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
export async function getReceiptPdf(userId, paymentId) {
|
|
269
|
+
const payment = await getFeePayment(paymentId);
|
|
270
|
+
if (!payment) {
|
|
271
|
+
throw new TRPCError({ code: "NOT_FOUND", message: "Payment not found" });
|
|
272
|
+
}
|
|
273
|
+
await requireAdmin(payment.schoolId, userId);
|
|
274
|
+
const invoice = payment.invoice;
|
|
275
|
+
const studentName = invoice.student.profile?.displayName || invoice.student.username;
|
|
276
|
+
const bytes = await buildFeePdf({
|
|
277
|
+
schoolName: invoice.school.name,
|
|
278
|
+
title: "Payment Receipt",
|
|
279
|
+
subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,
|
|
280
|
+
letterhead: invoice.school.letterhead
|
|
281
|
+
? {
|
|
282
|
+
path: invoice.school.letterhead.path,
|
|
283
|
+
type: invoice.school.letterhead.type,
|
|
284
|
+
}
|
|
285
|
+
: null,
|
|
286
|
+
lines: [
|
|
287
|
+
{ label: "Receipt ID", value: payment.id.slice(0, 8).toUpperCase() },
|
|
288
|
+
{ label: "Student", value: studentName },
|
|
289
|
+
{ label: "Period", value: monthLabel(invoice.year, invoice.month) },
|
|
290
|
+
{ label: "Amount paid", value: formatNpr(payment.amountNpr) },
|
|
291
|
+
{ label: "Method", value: payment.method },
|
|
292
|
+
{
|
|
293
|
+
label: "Reference",
|
|
294
|
+
value: payment.reference?.trim() || "—",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
label: "Paid at",
|
|
298
|
+
value: payment.paidAt.toISOString().slice(0, 10),
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
footerNote: "Manual ledger receipt — gateway integration deferred.",
|
|
302
|
+
});
|
|
303
|
+
return {
|
|
304
|
+
fileName: `receipt-${payment.id.slice(0, 8)}.pdf`,
|
|
305
|
+
contentType: "application/pdf",
|
|
306
|
+
base64: Buffer.from(bytes).toString("base64"),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
//# sourceMappingURL=fees.js.map
|
|
310
|
+
//# debugId=f88adefd-f266-510d-92db-2aeb2cb27715
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fees.js","sources":["services/fees.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Fee ledger service — school-admin gated monthly invoicing + payment recording.\n * Payment gateway intentionally deferred; this is a manual ledger.\n */\nimport { TRPCError } from \"@trpc/server\";\nimport { FeeInvoiceStatus, FeePaymentMethod } from \"@prisma/client\";\nimport { findActiveSchoolAdminMembership } from \"../models/school.js\";\nimport {\n createFeeInvoices,\n createFeePayment,\n createFeePlan,\n getFeeInvoice,\n getFeePayment,\n getFeePlan,\n listActiveStudentIds,\n listFeeInvoices,\n listFeePlans,\n listPlanEnrollmentIds,\n listStudentBalances,\n setFeePlanEnrollments,\n sumPaymentsForInvoice,\n updateFeePlan,\n updateInvoiceStatus,\n} from \"../models/fees.js\";\nimport {\n buildFeePdf,\n formatNpr,\n monthLabel,\n} from \"../lib/feePdf.js\";\n\nasync function requireAdmin(schoolId: string, userId: string) {\n const membership = await findActiveSchoolAdminMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Only school admins can manage fees\",\n });\n }\n return membership;\n}\n\nfunction assertMonth(month: number) {\n if (month < 1 || month > 12) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"month must be between 1 and 12\",\n });\n }\n}\n\nfunction invoicePaidTotal(payments: { amountNpr: number }[]): number {\n return payments.reduce((s, p) => s + p.amountNpr, 0);\n}\n\nfunction deriveStatus(\n amountNpr: number,\n paidNpr: number,\n current: FeeInvoiceStatus\n): FeeInvoiceStatus {\n if (current === \"VOID\") return \"VOID\";\n if (paidNpr <= 0) return \"UNPAID\";\n if (paidNpr >= amountNpr) return \"PAID\";\n return \"PARTIAL\";\n}\n\nfunction withBalance<\n T extends {\n amountNpr: number;\n status: FeeInvoiceStatus;\n payments: { amountNpr: number }[];\n },\n>(invoice: T) {\n const paidNpr = invoicePaidTotal(invoice.payments);\n return {\n ...invoice,\n paidNpr,\n balanceNpr: Math.max(0, invoice.amountNpr - paidNpr),\n };\n}\n\nexport async function listPlans(userId: string, schoolId: string) {\n await requireAdmin(schoolId, userId);\n const plans = await listFeePlans(schoolId);\n return { plans };\n}\n\nexport async function getPlan(userId: string, planId: string) {\n const plan = await getFeePlan(planId);\n if (!plan) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(plan.schoolId, userId);\n return { plan };\n}\n\nexport async function createPlan(\n userId: string,\n input: {\n schoolId: string;\n name: string;\n description?: string | null;\n amountNpr: number;\n studentIds?: string[];\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n const plan = await createFeePlan({\n schoolId: input.schoolId,\n name: input.name.trim(),\n description: input.description?.trim() || null,\n amountNpr: input.amountNpr,\n });\n if (input.studentIds?.length) {\n const updated = await setFeePlanEnrollments(plan.id, input.studentIds);\n return { plan: updated };\n }\n return { plan };\n}\n\nexport async function updatePlan(\n userId: string,\n input: {\n planId: string;\n name?: string;\n description?: string | null;\n amountNpr?: number;\n isActive?: boolean;\n }\n) {\n const existing = await getFeePlan(input.planId);\n if (!existing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(existing.schoolId, userId);\n if (input.amountNpr != null && input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n const plan = await updateFeePlan(input.planId, {\n name: input.name?.trim(),\n description: input.description,\n amountNpr: input.amountNpr,\n isActive: input.isActive,\n });\n return { plan };\n}\n\nexport async function setPlanStudents(\n userId: string,\n input: { planId: string; studentIds: string[] }\n) {\n const existing = await getFeePlan(input.planId);\n if (!existing) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n await requireAdmin(existing.schoolId, userId);\n const plan = await setFeePlanEnrollments(input.planId, input.studentIds);\n return { plan };\n}\n\nexport async function generateMonthlyInvoices(\n userId: string,\n input: {\n schoolId: string;\n feePlanId: string;\n year: number;\n month: number;\n studentIds?: string[];\n dueDate?: Date | null;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n assertMonth(input.month);\n\n const plan = await getFeePlan(input.feePlanId);\n if (!plan || plan.schoolId !== input.schoolId) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Fee plan not found\" });\n }\n if (!plan.isActive) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot generate invoices for an inactive fee plan\",\n });\n }\n\n let studentIds = input.studentIds;\n if (!studentIds?.length) {\n const enrolled = await listPlanEnrollmentIds(plan.id);\n studentIds = enrolled.length\n ? enrolled\n : await listActiveStudentIds(input.schoolId);\n }\n\n if (!studentIds.length) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"No students to invoice\",\n });\n }\n\n const result = await createFeeInvoices(\n studentIds.map((studentId) => ({\n schoolId: input.schoolId,\n feePlanId: plan.id,\n studentId,\n year: input.year,\n month: input.month,\n amountNpr: plan.amountNpr,\n currency: plan.currency,\n dueDate: input.dueDate ?? null,\n }))\n );\n\n const invoices = await listFeeInvoices({\n schoolId: input.schoolId,\n feePlanId: plan.id,\n year: input.year,\n month: input.month,\n });\n\n return {\n createdCount: result.count,\n invoices: invoices.map(withBalance),\n };\n}\n\nexport async function listInvoices(\n userId: string,\n input: {\n schoolId: string;\n year?: number;\n month?: number;\n studentId?: string;\n feePlanId?: string;\n status?: FeeInvoiceStatus;\n }\n) {\n await requireAdmin(input.schoolId, userId);\n if (input.month != null) assertMonth(input.month);\n const invoices = await listFeeInvoices(input);\n return { invoices: invoices.map(withBalance) };\n}\n\nexport async function getInvoice(userId: string, invoiceId: string) {\n const invoice = await getFeeInvoice(invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n return { invoice: withBalance(invoice) };\n}\n\nexport async function recordPayment(\n userId: string,\n input: {\n invoiceId: string;\n amountNpr: number;\n method: FeePaymentMethod;\n reference?: string | null;\n paidAt?: Date;\n notes?: string | null;\n }\n) {\n const invoice = await getFeeInvoice(input.invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n\n if (invoice.status === \"VOID\") {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"Cannot record payment on a void invoice\",\n });\n }\n if (input.amountNpr <= 0) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"amountNpr must be positive\",\n });\n }\n if (\n (input.method === \"BANK\" || input.method === \"ESEWA_REF\") &&\n !input.reference?.trim()\n ) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: \"reference is required for BANK and ESEWA_REF payments\",\n });\n }\n\n const paidSoFar = invoicePaidTotal(invoice.payments);\n const balance = Math.max(0, invoice.amountNpr - paidSoFar);\n if (input.amountNpr > balance) {\n throw new TRPCError({\n code: \"BAD_REQUEST\",\n message: `Payment exceeds balance (${formatNpr(balance)})`,\n });\n }\n\n const payment = await createFeePayment({\n schoolId: invoice.schoolId,\n invoiceId: invoice.id,\n amountNpr: input.amountNpr,\n method: input.method,\n reference: input.reference?.trim() || null,\n paidAt: input.paidAt,\n recordedById: userId,\n notes: input.notes?.trim() || null,\n });\n\n const aggregate = await sumPaymentsForInvoice(invoice.id);\n const newPaid = aggregate._sum.amountNpr ?? 0;\n const status = deriveStatus(invoice.amountNpr, newPaid, invoice.status);\n await updateInvoiceStatus(invoice.id, status);\n\n const refreshed = await getFeeInvoice(invoice.id);\n return {\n payment,\n invoice: refreshed ? withBalance(refreshed) : null,\n };\n}\n\nexport async function listBalances(userId: string, schoolId: string) {\n await requireAdmin(schoolId, userId);\n const balances = await listStudentBalances(schoolId);\n return { balances };\n}\n\nexport async function getInvoicePdf(userId: string, invoiceId: string) {\n const invoice = await getFeeInvoice(invoiceId);\n if (!invoice) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Invoice not found\" });\n }\n await requireAdmin(invoice.schoolId, userId);\n\n const paidNpr = invoicePaidTotal(invoice.payments);\n const balanceNpr = Math.max(0, invoice.amountNpr - paidNpr);\n const studentName =\n invoice.student.profile?.displayName || invoice.student.username;\n\n const bytes = await buildFeePdf({\n schoolName: invoice.school.name,\n title: \"Fee Invoice\",\n subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,\n letterhead: invoice.school.letterhead\n ? {\n path: invoice.school.letterhead.path,\n type: invoice.school.letterhead.type,\n }\n : null,\n lines: [\n { label: \"Invoice ID\", value: invoice.id.slice(0, 8).toUpperCase() },\n { label: \"Student\", value: studentName },\n { label: \"Period\", value: monthLabel(invoice.year, invoice.month) },\n { label: \"Plan\", value: invoice.feePlan.name },\n { label: \"Amount\", value: formatNpr(invoice.amountNpr) },\n { label: \"Paid\", value: formatNpr(paidNpr) },\n { label: \"Balance\", value: formatNpr(balanceNpr) },\n { label: \"Status\", value: invoice.status },\n ],\n footerNote: \"Manual ledger — not a gateway receipt. Keep for school records.\",\n });\n\n return {\n fileName: `invoice-${invoice.year}-${String(invoice.month).padStart(2, \"0\")}-${invoice.id.slice(0, 8)}.pdf`,\n contentType: \"application/pdf\",\n base64: Buffer.from(bytes).toString(\"base64\"),\n };\n}\n\nexport async function getReceiptPdf(userId: string, paymentId: string) {\n const payment = await getFeePayment(paymentId);\n if (!payment) {\n throw new TRPCError({ code: \"NOT_FOUND\", message: \"Payment not found\" });\n }\n await requireAdmin(payment.schoolId, userId);\n\n const invoice = payment.invoice;\n const studentName =\n invoice.student.profile?.displayName || invoice.student.username;\n\n const bytes = await buildFeePdf({\n schoolName: invoice.school.name,\n title: \"Payment Receipt\",\n subtitle: `${invoice.feePlan.name} · ${monthLabel(invoice.year, invoice.month)}`,\n letterhead: invoice.school.letterhead\n ? {\n path: invoice.school.letterhead.path,\n type: invoice.school.letterhead.type,\n }\n : null,\n lines: [\n { label: \"Receipt ID\", value: payment.id.slice(0, 8).toUpperCase() },\n { label: \"Student\", value: studentName },\n { label: \"Period\", value: monthLabel(invoice.year, invoice.month) },\n { label: \"Amount paid\", value: formatNpr(payment.amountNpr) },\n { label: \"Method\", value: payment.method },\n {\n label: \"Reference\",\n value: payment.reference?.trim() || \"—\",\n },\n {\n label: \"Paid at\",\n value: payment.paidAt.toISOString().slice(0, 10),\n },\n ],\n footerNote: \"Manual ledger receipt — gateway integration deferred.\",\n });\n\n return {\n fileName: `receipt-${payment.id.slice(0, 8)}.pdf`,\n contentType: \"application/pdf\",\n base64: Buffer.from(bytes).toString(\"base64\"),\n };\n}\n"],"names":[],"mappings":"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","debug_id":"f88adefd-f266-510d-92db-2aeb2cb27715"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* C7: Fees authorization stubs for ACCOUNTANT (+ ADMIN).
|
|
3
|
+
*
|
|
4
|
+
* A2 owns full fee models; this module only provides reusable gates that
|
|
5
|
+
* fee routers can call once they land.
|
|
6
|
+
*/
|
|
7
|
+
import { SchoolMembershipRole } from "@prisma/client";
|
|
8
|
+
export declare function requireSchoolRoles(schoolId: string, userId: string, allowed: SchoolMembershipRole[], message?: string): Promise<{
|
|
9
|
+
status: import(".prisma/client").$Enums.SchoolMembershipStatus;
|
|
10
|
+
id: string;
|
|
11
|
+
userId: string;
|
|
12
|
+
createdAt: Date;
|
|
13
|
+
updatedAt: Date;
|
|
14
|
+
schoolId: string;
|
|
15
|
+
role: import(".prisma/client").$Enums.SchoolMembershipRole;
|
|
16
|
+
}>;
|
|
17
|
+
/** Admin or accountant may create/update fee structures and record payments. */
|
|
18
|
+
export declare function requireFeeWriteAccess(schoolId: string, userId: string): Promise<{
|
|
19
|
+
status: import(".prisma/client").$Enums.SchoolMembershipStatus;
|
|
20
|
+
id: string;
|
|
21
|
+
userId: string;
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
updatedAt: Date;
|
|
24
|
+
schoolId: string;
|
|
25
|
+
role: import(".prisma/client").$Enums.SchoolMembershipRole;
|
|
26
|
+
}>;
|
|
27
|
+
/** Broad read access stub — parents/students should be scoped to own records by A2. */
|
|
28
|
+
export declare function requireFeeReadAccess(schoolId: string, userId: string): Promise<{
|
|
29
|
+
status: import(".prisma/client").$Enums.SchoolMembershipStatus;
|
|
30
|
+
id: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
createdAt: Date;
|
|
33
|
+
updatedAt: Date;
|
|
34
|
+
schoolId: string;
|
|
35
|
+
role: import(".prisma/client").$Enums.SchoolMembershipRole;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function canManageFees(role: SchoolMembershipRole): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Placeholder procedure surface until A2 fee models exist.
|
|
40
|
+
* Returns capability flags so the client can show/hide fee UI.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getFeeCapabilities(userId: string, schoolId: string): Promise<{
|
|
43
|
+
role: import(".prisma/client").$Enums.SchoolMembershipRole;
|
|
44
|
+
canManageFees: boolean;
|
|
45
|
+
canViewFees: boolean;
|
|
46
|
+
feesImplemented: false;
|
|
47
|
+
message: string;
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=feesAuthz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feesAuthz.d.ts","sourceRoot":"/","sources":["services/feesAuthz.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAatD,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,EAAE,EAC/B,OAAO,SAA+C;;;;;;;;GAOvD;AAED,gFAAgF;AAChF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;GAO3E;AAED,uFAAuF;AACvF,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;;;GAO1E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,WAEvD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;GAgBxE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a20bdfd8-5540-57d5-a34c-09ab90d0777b")}catch(e){}}();
|
|
3
|
+
import { TRPCError } from "@trpc/server";
|
|
4
|
+
import { findActiveSchoolMembership } from "../models/school.js";
|
|
5
|
+
const FEE_WRITE_ROLES = ["ADMIN", "ACCOUNTANT"];
|
|
6
|
+
const FEE_READ_ROLES = [
|
|
7
|
+
"ADMIN",
|
|
8
|
+
"ACCOUNTANT",
|
|
9
|
+
"TEACHER",
|
|
10
|
+
"PARENT",
|
|
11
|
+
"STUDENT",
|
|
12
|
+
];
|
|
13
|
+
export async function requireSchoolRoles(schoolId, userId, allowed, message = "You do not have permission for this action") {
|
|
14
|
+
const membership = await findActiveSchoolMembership(schoolId, userId);
|
|
15
|
+
if (!membership || !allowed.includes(membership.role)) {
|
|
16
|
+
throw new TRPCError({ code: "FORBIDDEN", message });
|
|
17
|
+
}
|
|
18
|
+
return membership;
|
|
19
|
+
}
|
|
20
|
+
/** Admin or accountant may create/update fee structures and record payments. */
|
|
21
|
+
export async function requireFeeWriteAccess(schoolId, userId) {
|
|
22
|
+
return requireSchoolRoles(schoolId, userId, FEE_WRITE_ROLES, "Only school admins or accountants can manage fees");
|
|
23
|
+
}
|
|
24
|
+
/** Broad read access stub — parents/students should be scoped to own records by A2. */
|
|
25
|
+
export async function requireFeeReadAccess(schoolId, userId) {
|
|
26
|
+
return requireSchoolRoles(schoolId, userId, FEE_READ_ROLES, "You are not a member of this school");
|
|
27
|
+
}
|
|
28
|
+
export function canManageFees(role) {
|
|
29
|
+
return FEE_WRITE_ROLES.includes(role);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Placeholder procedure surface until A2 fee models exist.
|
|
33
|
+
* Returns capability flags so the client can show/hide fee UI.
|
|
34
|
+
*/
|
|
35
|
+
export async function getFeeCapabilities(userId, schoolId) {
|
|
36
|
+
const membership = await findActiveSchoolMembership(schoolId, userId);
|
|
37
|
+
if (!membership) {
|
|
38
|
+
throw new TRPCError({
|
|
39
|
+
code: "FORBIDDEN",
|
|
40
|
+
message: "You are not a member of this school",
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
role: membership.role,
|
|
45
|
+
canManageFees: canManageFees(membership.role),
|
|
46
|
+
canViewFees: FEE_READ_ROLES.includes(membership.role),
|
|
47
|
+
feesImplemented: false,
|
|
48
|
+
message: "Fee models are not implemented on this branch yet (owned by A2). Authz stubs are ready.",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=feesAuthz.js.map
|
|
52
|
+
//# debugId=a20bdfd8-5540-57d5-a34c-09ab90d0777b
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feesAuthz.js","sources":["services/feesAuthz.ts"],"sourceRoot":"/","sourcesContent":["/**\n * C7: Fees authorization stubs for ACCOUNTANT (+ ADMIN).\n *\n * A2 owns full fee models; this module only provides reusable gates that\n * fee routers can call once they land.\n */\nimport { SchoolMembershipRole } from \"@prisma/client\";\nimport { TRPCError } from \"@trpc/server\";\nimport { findActiveSchoolMembership } from \"../models/school.js\";\n\nconst FEE_WRITE_ROLES: SchoolMembershipRole[] = [\"ADMIN\", \"ACCOUNTANT\"];\nconst FEE_READ_ROLES: SchoolMembershipRole[] = [\n \"ADMIN\",\n \"ACCOUNTANT\",\n \"TEACHER\",\n \"PARENT\",\n \"STUDENT\",\n];\n\nexport async function requireSchoolRoles(\n schoolId: string,\n userId: string,\n allowed: SchoolMembershipRole[],\n message = \"You do not have permission for this action\"\n) {\n const membership = await findActiveSchoolMembership(schoolId, userId);\n if (!membership || !allowed.includes(membership.role)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message });\n }\n return membership;\n}\n\n/** Admin or accountant may create/update fee structures and record payments. */\nexport async function requireFeeWriteAccess(schoolId: string, userId: string) {\n return requireSchoolRoles(\n schoolId,\n userId,\n FEE_WRITE_ROLES,\n \"Only school admins or accountants can manage fees\"\n );\n}\n\n/** Broad read access stub — parents/students should be scoped to own records by A2. */\nexport async function requireFeeReadAccess(schoolId: string, userId: string) {\n return requireSchoolRoles(\n schoolId,\n userId,\n FEE_READ_ROLES,\n \"You are not a member of this school\"\n );\n}\n\nexport function canManageFees(role: SchoolMembershipRole) {\n return FEE_WRITE_ROLES.includes(role);\n}\n\n/**\n * Placeholder procedure surface until A2 fee models exist.\n * Returns capability flags so the client can show/hide fee UI.\n */\nexport async function getFeeCapabilities(userId: string, schoolId: string) {\n const membership = await findActiveSchoolMembership(schoolId, userId);\n if (!membership) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"You are not a member of this school\",\n });\n }\n return {\n role: membership.role,\n canManageFees: canManageFees(membership.role),\n canViewFees: FEE_READ_ROLES.includes(membership.role),\n feesImplemented: false as const,\n message:\n \"Fee models are not implemented on this branch yet (owned by A2). Authz stubs are ready.\",\n };\n}\n"],"names":[],"mappings":";;AAOA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,MAAM,eAAe,GAA2B,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AACxE,MAAM,cAAc,GAA2B;IAC7C,OAAO;IACP,YAAY;IACZ,SAAS;IACT,QAAQ;IACR,SAAS;CACV,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,QAAgB,EAChB,MAAc,EACd,OAA+B,EAC/B,OAAO,GAAG,4CAA4C;IAEtD,MAAM,UAAU,GAAG,MAAM,0BAA0B,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,QAAgB,EAAE,MAAc;IAC1E,OAAO,kBAAkB,CACvB,QAAQ,EACR,MAAM,EACN,eAAe,EACf,mDAAmD,CACpD,CAAC;AACJ,CAAC;AAED,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,QAAgB,EAAE,MAAc;IACzE,OAAO,kBAAkB,CACvB,QAAQ,EACR,MAAM,EACN,cAAc,EACd,qCAAqC,CACtC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAA0B;IACtD,OAAO,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,QAAgB;IACvE,MAAM,UAAU,GAAG,MAAM,0BAA0B,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,SAAS,CAAC;YAClB,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,qCAAqC;SAC/C,CAAC,CAAC;IACL,CAAC;IACD,OAAO;QACL,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,aAAa,EAAE,aAAa,CAAC,UAAU,CAAC,IAAI,CAAC;QAC7C,WAAW,EAAE,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC;QACrD,eAAe,EAAE,KAAc;QAC/B,OAAO,EACL,yFAAyF;KAC5F,CAAC;AACJ,CAAC","debug_id":"a20bdfd8-5540-57d5-a34c-09ab90d0777b"}
|
package/dist/services/file.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ export declare function moveFile(fileId: string, targetFolderId: string): Promis
|
|
|
52
52
|
announcementId: string | null;
|
|
53
53
|
discussionId: string | null;
|
|
54
54
|
discussionReplyId: string | null;
|
|
55
|
+
moduleItemId: string | null;
|
|
55
56
|
schoolDevelopementProgramId: string | null;
|
|
56
57
|
}>;
|
|
57
58
|
export declare function renameFile(fileId: string, newName: string): Promise<{
|
|
@@ -87,6 +88,7 @@ export declare function renameFile(fileId: string, newName: string): Promise<{
|
|
|
87
88
|
announcementId: string | null;
|
|
88
89
|
discussionId: string | null;
|
|
89
90
|
discussionReplyId: string | null;
|
|
91
|
+
moduleItemId: string | null;
|
|
90
92
|
schoolDevelopementProgramId: string | null;
|
|
91
93
|
}>;
|
|
92
94
|
export declare function deleteFileRecord(fileId: string, classId: string): Promise<{
|
|
@@ -132,6 +134,7 @@ export declare function moveFileScoped(userId: string, input: {
|
|
|
132
134
|
announcementId: string | null;
|
|
133
135
|
discussionId: string | null;
|
|
134
136
|
discussionReplyId: string | null;
|
|
137
|
+
moduleItemId: string | null;
|
|
135
138
|
schoolDevelopementProgramId: string | null;
|
|
136
139
|
}>;
|
|
137
140
|
/** Scope-aware rename. */
|
|
@@ -171,6 +174,7 @@ export declare function renameFileScoped(userId: string, input: {
|
|
|
171
174
|
announcementId: string | null;
|
|
172
175
|
discussionId: string | null;
|
|
173
176
|
discussionReplyId: string | null;
|
|
177
|
+
moduleItemId: string | null;
|
|
174
178
|
schoolDevelopementProgramId: string | null;
|
|
175
179
|
}>;
|
|
176
180
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"/","sources":["services/file.ts"],"names":[],"mappings":"AA+CA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;GAkFpE;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"/","sources":["services/file.ts"],"names":[],"mappings":"AA+CA;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;GAkFpE;AAED,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmBvB;AAED,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkBhB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;GA6BrE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsBlD;AAED,0BAA0B;AAC1B,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsB3C;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;GA4BrE"}
|
|
@@ -75,6 +75,9 @@ export declare function assignAssignmentCategory(classId: string, assignmentId:
|
|
|
75
75
|
order: number | null;
|
|
76
76
|
gradingBoundaryId: string | null;
|
|
77
77
|
gradeCategoryId: string | null;
|
|
78
|
+
timeLimitMinutes: number | null;
|
|
79
|
+
singleAttempt: boolean;
|
|
80
|
+
allowLateSubmissions: boolean;
|
|
78
81
|
}>;
|
|
79
82
|
export declare function listCustomGradeColumns(classId: string): Promise<({
|
|
80
83
|
entries: {
|
|
@@ -129,8 +132,8 @@ export declare function setCustomGradeEntry(classId: string, columnId: string, s
|
|
|
129
132
|
id: string;
|
|
130
133
|
points: number;
|
|
131
134
|
studentId: string;
|
|
132
|
-
columnId: string;
|
|
133
135
|
note: string | null;
|
|
136
|
+
columnId: string;
|
|
134
137
|
} | null>;
|
|
135
138
|
export declare function getGradePolicies(classId: string): Promise<{
|
|
136
139
|
id: string;
|
|
@@ -191,6 +194,20 @@ export declare function getComputedGradebook(classId: string, userId: string): P
|
|
|
191
194
|
};
|
|
192
195
|
students: GradebookStudentRow[];
|
|
193
196
|
}>;
|
|
197
|
+
/**
|
|
198
|
+
* Compute one student's final grade in a class without teacher-auth filtering.
|
|
199
|
+
* Used by school-admin transcripts (caller must enforce school admin separately).
|
|
200
|
+
*/
|
|
201
|
+
export declare function computeStudentGradeInClass(classId: string, studentId: string): Promise<{
|
|
202
|
+
result: FinalGradeResult;
|
|
203
|
+
letterGrade: string | null;
|
|
204
|
+
gradingBoundary: {
|
|
205
|
+
id: string;
|
|
206
|
+
name: string | null;
|
|
207
|
+
structured: string;
|
|
208
|
+
} | null;
|
|
209
|
+
studentName: string | null;
|
|
210
|
+
} | null>;
|
|
194
211
|
export declare function exportGradebookCsv(classId: string, userId: string): Promise<{
|
|
195
212
|
csv: string;
|
|
196
213
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gradebook.d.ts","sourceRoot":"/","sources":["services/gradebook.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,iBAAiB,IAAI,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAY9E,OAAO,EAGL,KAAK,gBAAgB,EAGtB,MAAM,uBAAuB,CAAC;AAI/B,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM;;;;;;;KAExD;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;GAW/E;AAED,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;;;;;;;GAUF;AAED,wBAAsB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;GAM5E;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,GAAG,IAAI
|
|
1
|
+
{"version":3,"file":"gradebook.d.ts","sourceRoot":"/","sources":["services/gradebook.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8cc94c17-86dc-5a58-864f-1590c5dbf050")}catch(e){}}();
|
|
3
3
|
import { TRPCError } from "@trpc/server";
|
|
4
4
|
import { prisma } from "../lib/prisma.js";
|
|
5
5
|
import { isTeacherInClass } from "../models/class.js";
|
|
@@ -216,6 +216,61 @@ export async function getComputedGradebook(classId, userId) {
|
|
|
216
216
|
students: rows,
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Compute one student's final grade in a class without teacher-auth filtering.
|
|
221
|
+
* Used by school-admin transcripts (caller must enforce school admin separately).
|
|
222
|
+
*/
|
|
223
|
+
export async function computeStudentGradeInClass(classId, studentId) {
|
|
224
|
+
const [classData, categories, columns] = await Promise.all([
|
|
225
|
+
findClassForGradebook(classId),
|
|
226
|
+
findGradeCategoriesByClassId(classId),
|
|
227
|
+
findCustomGradeColumnsByClassId(classId),
|
|
228
|
+
]);
|
|
229
|
+
if (!classData)
|
|
230
|
+
return null;
|
|
231
|
+
const student = classData.students.find((s) => s.id === studentId);
|
|
232
|
+
if (!student)
|
|
233
|
+
return null;
|
|
234
|
+
const policies = {
|
|
235
|
+
treatMissingAsZero: classData.treatMissingAsZero,
|
|
236
|
+
roundingMode: classData.gradeRoundingMode,
|
|
237
|
+
};
|
|
238
|
+
const categoryInputs = categories.map((category) => ({
|
|
239
|
+
id: category.id,
|
|
240
|
+
name: category.name,
|
|
241
|
+
weight: category.weight,
|
|
242
|
+
dropLowest: category.dropLowest,
|
|
243
|
+
}));
|
|
244
|
+
const boundary = classData.gradingBoundaries[0] ?? null;
|
|
245
|
+
const scores = [];
|
|
246
|
+
for (const assignment of classData.assignments) {
|
|
247
|
+
const submission = assignment.submissions.find((item) => item.studentId === studentId);
|
|
248
|
+
scores.push({
|
|
249
|
+
categoryId: assignment.gradeCategoryId,
|
|
250
|
+
pointsEarned: submission?.gradeReceived ?? null,
|
|
251
|
+
maxPoints: assignment.maxGrade ?? 0,
|
|
252
|
+
weight: assignment.weight,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
for (const column of columns) {
|
|
256
|
+
const entry = column.entries.find((item) => item.studentId === studentId);
|
|
257
|
+
scores.push({
|
|
258
|
+
categoryId: column.gradeCategoryId,
|
|
259
|
+
pointsEarned: entry?.points ?? null,
|
|
260
|
+
maxPoints: column.maxPoints,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const result = computeFinalGrade(policies, categoryInputs, scores);
|
|
264
|
+
const letterGrade = boundary && result.percentage !== null
|
|
265
|
+
? resolveLetterGrade(boundary.structured, result.percentage)
|
|
266
|
+
: null;
|
|
267
|
+
return {
|
|
268
|
+
result,
|
|
269
|
+
letterGrade,
|
|
270
|
+
gradingBoundary: boundary,
|
|
271
|
+
studentName: student.profile?.displayName || student.username,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
219
274
|
// ===== CSV export =====
|
|
220
275
|
function escapeCsvCell(value) {
|
|
221
276
|
if (/[",\n\r]/.test(value)) {
|
|
@@ -260,4 +315,4 @@ export async function exportGradebookCsv(classId, userId) {
|
|
|
260
315
|
return { csv: lines.join("\n") };
|
|
261
316
|
}
|
|
262
317
|
//# sourceMappingURL=gradebook.js.map
|
|
263
|
-
//# debugId=
|
|
318
|
+
//# debugId=8cc94c17-86dc-5a58-864f-1590c5dbf050
|