@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.
- 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 +193 -0
- package/dist/models/school.d.ts.map +1 -1
- package/dist/models/school.js +80 -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 +11605 -7133
- 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 +476 -6
- package/dist/routers/school.d.ts.map +1 -1
- package/dist/routers/school.js +130 -4
- 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 +122 -3
- package/dist/services/school.d.ts.map +1 -1
- package/dist/services/school.js +179 -47
- 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 +95 -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 +184 -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 +243 -45
- 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,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded transcript / progress-report PDF builder (pdf-lib).
|
|
3
|
+
*
|
|
4
|
+
* Variants:
|
|
5
|
+
* - `letter` — percentage + letter-grade style transcript
|
|
6
|
+
* - `level` — CDC Annex-4 style progress report (Below Basic / Basic /
|
|
7
|
+
* Proficient / Advanced) when grading boundaries are level-based
|
|
8
|
+
*
|
|
9
|
+
* School branding uses name + optional logo. Letterhead is not in the School
|
|
10
|
+
* schema yet — when added, callers can pass `letterheadBytes` without blocking.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
!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]="1384d4a1-636c-51e0-8dbd-e4271dae1aac")}catch(e){}}();
|
|
14
|
+
import { PDFDocument, rgb, StandardFonts } from "pdf-lib";
|
|
15
|
+
import fontkit from "@pdf-lib/fontkit";
|
|
16
|
+
import { readFileSync } from "fs";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
const PAGE_WIDTH = 595.28; // A4
|
|
19
|
+
const PAGE_HEIGHT = 841.89;
|
|
20
|
+
const MARGIN = 48;
|
|
21
|
+
const CONTENT_WIDTH = PAGE_WIDTH - MARGIN * 2;
|
|
22
|
+
const LEVEL_ORDER = [
|
|
23
|
+
"Below Basic",
|
|
24
|
+
"Basic",
|
|
25
|
+
"Proficient",
|
|
26
|
+
"Advanced",
|
|
27
|
+
];
|
|
28
|
+
const LEVEL_COLORS = {
|
|
29
|
+
"Below Basic": rgb(0.75, 0.22, 0.17),
|
|
30
|
+
Basic: rgb(0.85, 0.55, 0.13),
|
|
31
|
+
Proficient: rgb(0.2, 0.45, 0.7),
|
|
32
|
+
Advanced: rgb(0.13, 0.55, 0.35),
|
|
33
|
+
};
|
|
34
|
+
async function embedFonts(pdfDoc) {
|
|
35
|
+
pdfDoc.registerFontkit(fontkit);
|
|
36
|
+
const fontDir = join(process.cwd(), "src", "lib");
|
|
37
|
+
try {
|
|
38
|
+
const regularBytes = readFileSync(join(fontDir, "NotoSans-Regular.ttf"));
|
|
39
|
+
const boldBytes = readFileSync(join(fontDir, "NotoSans-Bold.ttf"));
|
|
40
|
+
return {
|
|
41
|
+
regular: await pdfDoc.embedFont(regularBytes),
|
|
42
|
+
bold: await pdfDoc.embedFont(boldBytes),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return {
|
|
47
|
+
regular: await pdfDoc.embedFont(StandardFonts.Helvetica),
|
|
48
|
+
bold: await pdfDoc.embedFont(StandardFonts.HelveticaBold),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function embedImage(pdfDoc, bytes, contentType) {
|
|
53
|
+
if (!bytes || bytes.length === 0)
|
|
54
|
+
return null;
|
|
55
|
+
const type = (contentType ?? "").toLowerCase();
|
|
56
|
+
try {
|
|
57
|
+
if (type.includes("png") || (!type && looksLikePng(bytes))) {
|
|
58
|
+
return await pdfDoc.embedPng(bytes);
|
|
59
|
+
}
|
|
60
|
+
return await pdfDoc.embedJpg(bytes);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
try {
|
|
64
|
+
return await pdfDoc.embedPng(bytes);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
try {
|
|
68
|
+
return await pdfDoc.embedJpg(bytes);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function looksLikePng(bytes) {
|
|
77
|
+
return (bytes.length >= 8 &&
|
|
78
|
+
bytes[0] === 0x89 &&
|
|
79
|
+
bytes[1] === 0x50 &&
|
|
80
|
+
bytes[2] === 0x4e &&
|
|
81
|
+
bytes[3] === 0x47);
|
|
82
|
+
}
|
|
83
|
+
function formatPercent(value) {
|
|
84
|
+
if (value === null || Number.isNaN(value))
|
|
85
|
+
return "—";
|
|
86
|
+
return `${value.toFixed(1)}%`;
|
|
87
|
+
}
|
|
88
|
+
function drawText(page, text, x, y, font, size, color = rgb(0.12, 0.12, 0.14)) {
|
|
89
|
+
page.drawText(text, { x, y, size, font, color });
|
|
90
|
+
}
|
|
91
|
+
function truncateToWidth(text, font, size, maxWidth) {
|
|
92
|
+
if (font.widthOfTextAtSize(text, size) <= maxWidth)
|
|
93
|
+
return text;
|
|
94
|
+
let truncated = text;
|
|
95
|
+
while (truncated.length > 1) {
|
|
96
|
+
truncated = truncated.slice(0, -1);
|
|
97
|
+
if (font.widthOfTextAtSize(`${truncated}…`, size) <= maxWidth) {
|
|
98
|
+
return `${truncated}…`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return "…";
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Detects CDC Annex-4 style level boundaries from a GradingBoundary
|
|
105
|
+
* `structured` JSON payload.
|
|
106
|
+
*/
|
|
107
|
+
export function isLevelBasedBoundary(structured) {
|
|
108
|
+
if (!structured)
|
|
109
|
+
return false;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(structured);
|
|
112
|
+
if (!Array.isArray(parsed.boundaries) || parsed.boundaries.length === 0) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const grades = parsed.boundaries
|
|
116
|
+
.map((b) => (b.grade ?? "").trim().toLowerCase())
|
|
117
|
+
.filter(Boolean);
|
|
118
|
+
if (grades.length === 0)
|
|
119
|
+
return false;
|
|
120
|
+
const levelSet = new Set(LEVEL_ORDER.map((g) => g.toLowerCase()));
|
|
121
|
+
const matches = grades.filter((g) => levelSet.has(g));
|
|
122
|
+
return matches.length >= Math.ceil(grades.length * 0.5);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
export async function buildTranscriptPdf(input) {
|
|
129
|
+
const pdfDoc = await PDFDocument.create();
|
|
130
|
+
const { regular, bold } = await embedFonts(pdfDoc);
|
|
131
|
+
const logo = await embedImage(pdfDoc, input.logoBytes, input.logoContentType);
|
|
132
|
+
const letterhead = await embedImage(pdfDoc, input.letterheadBytes, input.letterheadContentType);
|
|
133
|
+
let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
134
|
+
let y = PAGE_HEIGHT - MARGIN;
|
|
135
|
+
if (letterhead) {
|
|
136
|
+
const lhHeight = 72;
|
|
137
|
+
const scale = Math.min(CONTENT_WIDTH / letterhead.width, lhHeight / letterhead.height);
|
|
138
|
+
const w = letterhead.width * scale;
|
|
139
|
+
const h = letterhead.height * scale;
|
|
140
|
+
page.drawImage(letterhead, {
|
|
141
|
+
x: MARGIN + (CONTENT_WIDTH - w) / 2,
|
|
142
|
+
y: y - h,
|
|
143
|
+
width: w,
|
|
144
|
+
height: h,
|
|
145
|
+
});
|
|
146
|
+
y -= h + 16;
|
|
147
|
+
}
|
|
148
|
+
const logoSize = 48;
|
|
149
|
+
let textLeft = MARGIN;
|
|
150
|
+
if (logo) {
|
|
151
|
+
const scale = Math.min(logoSize / logo.width, logoSize / logo.height);
|
|
152
|
+
const w = logo.width * scale;
|
|
153
|
+
const h = logo.height * scale;
|
|
154
|
+
page.drawImage(logo, {
|
|
155
|
+
x: MARGIN,
|
|
156
|
+
y: y - h,
|
|
157
|
+
width: w,
|
|
158
|
+
height: h,
|
|
159
|
+
});
|
|
160
|
+
textLeft = MARGIN + w + 14;
|
|
161
|
+
}
|
|
162
|
+
const title = input.variant === "level"
|
|
163
|
+
? "Student Progress Report"
|
|
164
|
+
: "Academic Transcript";
|
|
165
|
+
drawText(page, input.schoolName, textLeft, y - 16, bold, 16);
|
|
166
|
+
drawText(page, title, textLeft, y - 34, regular, 11, rgb(0.35, 0.35, 0.4));
|
|
167
|
+
y -= logo || letterhead ? 64 : 48;
|
|
168
|
+
page.drawLine({
|
|
169
|
+
start: { x: MARGIN, y },
|
|
170
|
+
end: { x: PAGE_WIDTH - MARGIN, y },
|
|
171
|
+
thickness: 1,
|
|
172
|
+
color: rgb(0.82, 0.82, 0.85),
|
|
173
|
+
});
|
|
174
|
+
y -= 22;
|
|
175
|
+
drawText(page, "Student", MARGIN, y, regular, 9, rgb(0.45, 0.45, 0.5));
|
|
176
|
+
y -= 14;
|
|
177
|
+
drawText(page, input.studentName, MARGIN, y, bold, 13);
|
|
178
|
+
y -= 16;
|
|
179
|
+
if (input.studentUsername) {
|
|
180
|
+
drawText(page, `@${input.studentUsername}`, MARGIN, y, regular, 9, rgb(0.4, 0.4, 0.45));
|
|
181
|
+
y -= 14;
|
|
182
|
+
}
|
|
183
|
+
const metaParts = [];
|
|
184
|
+
if (input.schoolYear?.trim()) {
|
|
185
|
+
metaParts.push(`School year: ${input.schoolYear.trim()}`);
|
|
186
|
+
}
|
|
187
|
+
if (input.periodLabel?.trim()) {
|
|
188
|
+
metaParts.push(`Period: ${input.periodLabel.trim()}`);
|
|
189
|
+
}
|
|
190
|
+
if (metaParts.length === 0) {
|
|
191
|
+
metaParts.push("Period: All recorded grades & attendance");
|
|
192
|
+
}
|
|
193
|
+
drawText(page, metaParts.join(" · "), MARGIN, y, regular, 9, rgb(0.35, 0.35, 0.4));
|
|
194
|
+
y -= 24;
|
|
195
|
+
y = drawAttendanceBlock(page, y, input.attendance, regular, bold);
|
|
196
|
+
if (input.variant === "level") {
|
|
197
|
+
y = drawLevelLegend(page, y, regular, bold);
|
|
198
|
+
}
|
|
199
|
+
const reportTitle = input.variant === "level" ? "Subject progress" : "Course grades";
|
|
200
|
+
drawText(page, reportTitle, MARGIN, y, bold, 11);
|
|
201
|
+
y -= 16;
|
|
202
|
+
y = drawTableHeader(page, y, input.variant, bold);
|
|
203
|
+
if (input.classes.length === 0) {
|
|
204
|
+
drawText(page, "No enrolled classes with grade data for this period.", MARGIN, y, regular, 9, rgb(0.45, 0.45, 0.5));
|
|
205
|
+
y -= 18;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
for (const row of input.classes) {
|
|
209
|
+
if (y < MARGIN + 80) {
|
|
210
|
+
page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
|
|
211
|
+
y = PAGE_HEIGHT - MARGIN;
|
|
212
|
+
y = drawTableHeader(page, y, input.variant, bold);
|
|
213
|
+
}
|
|
214
|
+
y = drawClassRow(page, y, row, input.variant, regular, bold);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const generated = input.generatedAt ?? new Date();
|
|
218
|
+
const footer = `Generated ${generated.toISOString().slice(0, 10)} · Studious LMS`;
|
|
219
|
+
drawText(page, footer, MARGIN, MARGIN - 8, regular, 8, rgb(0.55, 0.55, 0.58));
|
|
220
|
+
drawText(page, "Note: Academic terms (C1) are not modelled yet; period filters use dates or a school-year label.", MARGIN, MARGIN - 20, regular, 7, rgb(0.6, 0.6, 0.62));
|
|
221
|
+
return pdfDoc.save();
|
|
222
|
+
}
|
|
223
|
+
function drawAttendanceBlock(page, y, attendance, regular, bold) {
|
|
224
|
+
drawText(page, "Attendance", MARGIN, y, bold, 11);
|
|
225
|
+
y -= 14;
|
|
226
|
+
const line = [
|
|
227
|
+
`Present: ${attendance.present}`,
|
|
228
|
+
`Late: ${attendance.late}`,
|
|
229
|
+
`Absent: ${attendance.absent}`,
|
|
230
|
+
`Rate: ${formatPercent(attendance.percent)}`,
|
|
231
|
+
].join(" · ");
|
|
232
|
+
drawText(page, line, MARGIN, y, regular, 9, rgb(0.25, 0.25, 0.3));
|
|
233
|
+
y -= 8;
|
|
234
|
+
drawText(page, "Rate = (present + late) / (present + late + absent). Excused status is not modelled in this report path.", MARGIN, y, regular, 7, rgb(0.55, 0.55, 0.58));
|
|
235
|
+
return y - 20;
|
|
236
|
+
}
|
|
237
|
+
function drawLevelLegend(page, y, regular, bold) {
|
|
238
|
+
drawText(page, "Achievement levels (CDC Annex-4 style)", MARGIN, y, bold, 10);
|
|
239
|
+
y -= 14;
|
|
240
|
+
let x = MARGIN;
|
|
241
|
+
for (const level of LEVEL_ORDER) {
|
|
242
|
+
const color = LEVEL_COLORS[level];
|
|
243
|
+
page.drawRectangle({
|
|
244
|
+
x,
|
|
245
|
+
y: y - 2,
|
|
246
|
+
width: 8,
|
|
247
|
+
height: 8,
|
|
248
|
+
color,
|
|
249
|
+
});
|
|
250
|
+
drawText(page, level, x + 12, y - 1, regular, 8, color);
|
|
251
|
+
x += level.length * 8 * 0.52 + 28;
|
|
252
|
+
}
|
|
253
|
+
return y - 22;
|
|
254
|
+
}
|
|
255
|
+
function drawTableHeader(page, y, variant, bold) {
|
|
256
|
+
const gradeCol = variant === "level" ? "Level" : "Grade";
|
|
257
|
+
page.drawRectangle({
|
|
258
|
+
x: MARGIN,
|
|
259
|
+
y: y - 4,
|
|
260
|
+
width: CONTENT_WIDTH,
|
|
261
|
+
height: 18,
|
|
262
|
+
color: rgb(0.94, 0.95, 0.97),
|
|
263
|
+
});
|
|
264
|
+
drawText(page, "Subject / Class", MARGIN + 6, y, bold, 8);
|
|
265
|
+
drawText(page, "Section", MARGIN + 250, y, bold, 8);
|
|
266
|
+
drawText(page, "Score", MARGIN + 320, y, bold, 8);
|
|
267
|
+
drawText(page, gradeCol, MARGIN + 390, y, bold, 8);
|
|
268
|
+
return y - 20;
|
|
269
|
+
}
|
|
270
|
+
function drawClassRow(page, y, row, variant, regular, bold) {
|
|
271
|
+
const label = truncateToWidth(row.subject?.trim()
|
|
272
|
+
? `${row.subject} — ${row.className}`
|
|
273
|
+
: row.className, regular, 9, 230);
|
|
274
|
+
drawText(page, label, MARGIN + 6, y, regular, 9);
|
|
275
|
+
drawText(page, truncateToWidth(row.section || "—", regular, 9, 60), MARGIN + 250, y, regular, 9);
|
|
276
|
+
drawText(page, formatPercent(row.percentage), MARGIN + 320, y, regular, 9);
|
|
277
|
+
const grade = row.gradeLabel?.trim() || "—";
|
|
278
|
+
const levelKey = LEVEL_ORDER.find((l) => l.toLowerCase() === grade.toLowerCase());
|
|
279
|
+
const gradeColor = variant === "level" && levelKey
|
|
280
|
+
? LEVEL_COLORS[levelKey]
|
|
281
|
+
: rgb(0.12, 0.12, 0.14);
|
|
282
|
+
drawText(page, truncateToWidth(grade, bold, 9, 120), MARGIN + 390, y, bold, 9, gradeColor);
|
|
283
|
+
y -= 14;
|
|
284
|
+
if (row.categorySummaries.length > 0) {
|
|
285
|
+
const cats = row.categorySummaries
|
|
286
|
+
.filter((c) => c.average !== null)
|
|
287
|
+
.map((c) => `${c.name}: ${formatPercent(c.average)}`)
|
|
288
|
+
.join(" · ");
|
|
289
|
+
if (cats) {
|
|
290
|
+
drawText(page, truncateToWidth(cats, regular, 7, CONTENT_WIDTH - 12), MARGIN + 6, y, regular, 7, rgb(0.45, 0.45, 0.5));
|
|
291
|
+
y -= 12;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
page.drawLine({
|
|
295
|
+
start: { x: MARGIN, y: y + 4 },
|
|
296
|
+
end: { x: PAGE_WIDTH - MARGIN, y: y + 4 },
|
|
297
|
+
thickness: 0.5,
|
|
298
|
+
color: rgb(0.9, 0.9, 0.92),
|
|
299
|
+
});
|
|
300
|
+
return y - 6;
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=transcriptPdf.js.map
|
|
303
|
+
//# debugId=1384d4a1-636c-51e0-8dbd-e4271dae1aac
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcriptPdf.js","sources":["lib/transcriptPdf.ts"],"sourceRoot":"/","sourcesContent":["/**\n * Branded transcript / progress-report PDF builder (pdf-lib).\n *\n * Variants:\n * - `letter` — percentage + letter-grade style transcript\n * - `level` — CDC Annex-4 style progress report (Below Basic / Basic /\n * Proficient / Advanced) when grading boundaries are level-based\n *\n * School branding uses name + optional logo. Letterhead is not in the School\n * schema yet — when added, callers can pass `letterheadBytes` without blocking.\n */\n\nimport { PDFDocument, PDFFont, PDFPage, PDFImage, rgb, StandardFonts } from \"pdf-lib\";\nimport fontkit from \"@pdf-lib/fontkit\";\nimport { readFileSync } from \"fs\";\nimport { join } from \"path\";\n\nexport type TranscriptReportVariant = \"letter\" | \"level\";\n\nexport interface TranscriptClassRow {\n className: string;\n subject: string;\n section: string;\n percentage: number | null;\n gradeLabel: string | null;\n categorySummaries: { name: string; average: number | null }[];\n}\n\nexport interface TranscriptAttendanceSummary {\n present: number;\n late: number;\n absent: number;\n total: number;\n /** (present + late) / total * 100, or null when total is 0. */\n percent: number | null;\n}\n\nexport interface TranscriptPdfInput {\n schoolName: string;\n studentName: string;\n studentUsername?: string | null;\n /** Free-text school year (no AcademicTerm model yet — see C1). */\n schoolYear?: string | null;\n periodLabel?: string | null;\n variant: TranscriptReportVariant;\n classes: TranscriptClassRow[];\n attendance: TranscriptAttendanceSummary;\n logoBytes?: Uint8Array | null;\n logoContentType?: string | null;\n /** Optional future letterhead image; ignored when null/undefined. */\n letterheadBytes?: Uint8Array | null;\n letterheadContentType?: string | null;\n generatedAt?: Date;\n}\n\nconst PAGE_WIDTH = 595.28; // A4\nconst PAGE_HEIGHT = 841.89;\nconst MARGIN = 48;\nconst CONTENT_WIDTH = PAGE_WIDTH - MARGIN * 2;\n\nconst LEVEL_ORDER = [\n \"Below Basic\",\n \"Basic\",\n \"Proficient\",\n \"Advanced\",\n] as const;\n\nconst LEVEL_COLORS: Record<(typeof LEVEL_ORDER)[number], ReturnType<typeof rgb>> = {\n \"Below Basic\": rgb(0.75, 0.22, 0.17),\n Basic: rgb(0.85, 0.55, 0.13),\n Proficient: rgb(0.2, 0.45, 0.7),\n Advanced: rgb(0.13, 0.55, 0.35),\n};\n\nasync function embedFonts(pdfDoc: PDFDocument): Promise<{\n regular: PDFFont;\n bold: PDFFont;\n}> {\n pdfDoc.registerFontkit(fontkit);\n const fontDir = join(process.cwd(), \"src\", \"lib\");\n try {\n const regularBytes = readFileSync(join(fontDir, \"NotoSans-Regular.ttf\"));\n const boldBytes = readFileSync(join(fontDir, \"NotoSans-Bold.ttf\"));\n return {\n regular: await pdfDoc.embedFont(regularBytes),\n bold: await pdfDoc.embedFont(boldBytes),\n };\n } catch {\n return {\n regular: await pdfDoc.embedFont(StandardFonts.Helvetica),\n bold: await pdfDoc.embedFont(StandardFonts.HelveticaBold),\n };\n }\n}\n\nasync function embedImage(\n pdfDoc: PDFDocument,\n bytes: Uint8Array | null | undefined,\n contentType?: string | null\n): Promise<PDFImage | null> {\n if (!bytes || bytes.length === 0) return null;\n const type = (contentType ?? \"\").toLowerCase();\n try {\n if (type.includes(\"png\") || (!type && looksLikePng(bytes))) {\n return await pdfDoc.embedPng(bytes);\n }\n return await pdfDoc.embedJpg(bytes);\n } catch {\n try {\n return await pdfDoc.embedPng(bytes);\n } catch {\n try {\n return await pdfDoc.embedJpg(bytes);\n } catch {\n return null;\n }\n }\n }\n}\n\nfunction looksLikePng(bytes: Uint8Array): boolean {\n return (\n bytes.length >= 8 &&\n bytes[0] === 0x89 &&\n bytes[1] === 0x50 &&\n bytes[2] === 0x4e &&\n bytes[3] === 0x47\n );\n}\n\nfunction formatPercent(value: number | null): string {\n if (value === null || Number.isNaN(value)) return \"—\";\n return `${value.toFixed(1)}%`;\n}\n\nfunction drawText(\n page: PDFPage,\n text: string,\n x: number,\n y: number,\n font: PDFFont,\n size: number,\n color = rgb(0.12, 0.12, 0.14)\n) {\n page.drawText(text, { x, y, size, font, color });\n}\n\nfunction truncateToWidth(\n text: string,\n font: PDFFont,\n size: number,\n maxWidth: number\n): string {\n if (font.widthOfTextAtSize(text, size) <= maxWidth) return text;\n let truncated = text;\n while (truncated.length > 1) {\n truncated = truncated.slice(0, -1);\n if (font.widthOfTextAtSize(`${truncated}…`, size) <= maxWidth) {\n return `${truncated}…`;\n }\n }\n return \"…\";\n}\n\n/**\n * Detects CDC Annex-4 style level boundaries from a GradingBoundary\n * `structured` JSON payload.\n */\nexport function isLevelBasedBoundary(structured: string | null | undefined): boolean {\n if (!structured) return false;\n try {\n const parsed = JSON.parse(structured) as {\n boundaries?: { grade?: string }[];\n };\n if (!Array.isArray(parsed.boundaries) || parsed.boundaries.length === 0) {\n return false;\n }\n const grades = parsed.boundaries\n .map((b) => (b.grade ?? \"\").trim().toLowerCase())\n .filter(Boolean);\n if (grades.length === 0) return false;\n\n const levelSet = new Set(LEVEL_ORDER.map((g) => g.toLowerCase()));\n const matches = grades.filter((g) => levelSet.has(g));\n return matches.length >= Math.ceil(grades.length * 0.5);\n } catch {\n return false;\n }\n}\n\nexport async function buildTranscriptPdf(\n input: TranscriptPdfInput\n): Promise<Uint8Array> {\n const pdfDoc = await PDFDocument.create();\n const { regular, bold } = await embedFonts(pdfDoc);\n const logo = await embedImage(pdfDoc, input.logoBytes, input.logoContentType);\n const letterhead = await embedImage(\n pdfDoc,\n input.letterheadBytes,\n input.letterheadContentType\n );\n\n let page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);\n let y = PAGE_HEIGHT - MARGIN;\n\n if (letterhead) {\n const lhHeight = 72;\n const scale = Math.min(\n CONTENT_WIDTH / letterhead.width,\n lhHeight / letterhead.height\n );\n const w = letterhead.width * scale;\n const h = letterhead.height * scale;\n page.drawImage(letterhead, {\n x: MARGIN + (CONTENT_WIDTH - w) / 2,\n y: y - h,\n width: w,\n height: h,\n });\n y -= h + 16;\n }\n\n const logoSize = 48;\n let textLeft = MARGIN;\n if (logo) {\n const scale = Math.min(logoSize / logo.width, logoSize / logo.height);\n const w = logo.width * scale;\n const h = logo.height * scale;\n page.drawImage(logo, {\n x: MARGIN,\n y: y - h,\n width: w,\n height: h,\n });\n textLeft = MARGIN + w + 14;\n }\n\n const title =\n input.variant === \"level\"\n ? \"Student Progress Report\"\n : \"Academic Transcript\";\n\n drawText(page, input.schoolName, textLeft, y - 16, bold, 16);\n drawText(page, title, textLeft, y - 34, regular, 11, rgb(0.35, 0.35, 0.4));\n y -= logo || letterhead ? 64 : 48;\n\n page.drawLine({\n start: { x: MARGIN, y },\n end: { x: PAGE_WIDTH - MARGIN, y },\n thickness: 1,\n color: rgb(0.82, 0.82, 0.85),\n });\n y -= 22;\n\n drawText(page, \"Student\", MARGIN, y, regular, 9, rgb(0.45, 0.45, 0.5));\n y -= 14;\n drawText(page, input.studentName, MARGIN, y, bold, 13);\n y -= 16;\n if (input.studentUsername) {\n drawText(\n page,\n `@${input.studentUsername}`,\n MARGIN,\n y,\n regular,\n 9,\n rgb(0.4, 0.4, 0.45)\n );\n y -= 14;\n }\n\n const metaParts: string[] = [];\n if (input.schoolYear?.trim()) {\n metaParts.push(`School year: ${input.schoolYear.trim()}`);\n }\n if (input.periodLabel?.trim()) {\n metaParts.push(`Period: ${input.periodLabel.trim()}`);\n }\n if (metaParts.length === 0) {\n metaParts.push(\"Period: All recorded grades & attendance\");\n }\n drawText(\n page,\n metaParts.join(\" · \"),\n MARGIN,\n y,\n regular,\n 9,\n rgb(0.35, 0.35, 0.4)\n );\n y -= 24;\n\n y = drawAttendanceBlock(page, y, input.attendance, regular, bold);\n\n if (input.variant === \"level\") {\n y = drawLevelLegend(page, y, regular, bold);\n }\n\n const reportTitle =\n input.variant === \"level\" ? \"Subject progress\" : \"Course grades\";\n drawText(page, reportTitle, MARGIN, y, bold, 11);\n y -= 16;\n\n y = drawTableHeader(page, y, input.variant, bold);\n\n if (input.classes.length === 0) {\n drawText(\n page,\n \"No enrolled classes with grade data for this period.\",\n MARGIN,\n y,\n regular,\n 9,\n rgb(0.45, 0.45, 0.5)\n );\n y -= 18;\n } else {\n for (const row of input.classes) {\n if (y < MARGIN + 80) {\n page = pdfDoc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);\n y = PAGE_HEIGHT - MARGIN;\n y = drawTableHeader(page, y, input.variant, bold);\n }\n y = drawClassRow(page, y, row, input.variant, regular, bold);\n }\n }\n\n const generated = input.generatedAt ?? new Date();\n const footer = `Generated ${generated.toISOString().slice(0, 10)} · Studious LMS`;\n drawText(\n page,\n footer,\n MARGIN,\n MARGIN - 8,\n regular,\n 8,\n rgb(0.55, 0.55, 0.58)\n );\n\n drawText(\n page,\n \"Note: Academic terms (C1) are not modelled yet; period filters use dates or a school-year label.\",\n MARGIN,\n MARGIN - 20,\n regular,\n 7,\n rgb(0.6, 0.6, 0.62)\n );\n\n return pdfDoc.save();\n}\n\nfunction drawAttendanceBlock(\n page: PDFPage,\n y: number,\n attendance: TranscriptAttendanceSummary,\n regular: PDFFont,\n bold: PDFFont\n): number {\n drawText(page, \"Attendance\", MARGIN, y, bold, 11);\n y -= 14;\n\n const line = [\n `Present: ${attendance.present}`,\n `Late: ${attendance.late}`,\n `Absent: ${attendance.absent}`,\n `Rate: ${formatPercent(attendance.percent)}`,\n ].join(\" · \");\n\n drawText(page, line, MARGIN, y, regular, 9, rgb(0.25, 0.25, 0.3));\n y -= 8;\n drawText(\n page,\n \"Rate = (present + late) / (present + late + absent). Excused status is not modelled in this report path.\",\n MARGIN,\n y,\n regular,\n 7,\n rgb(0.55, 0.55, 0.58)\n );\n return y - 20;\n}\n\nfunction drawLevelLegend(\n page: PDFPage,\n y: number,\n regular: PDFFont,\n bold: PDFFont\n): number {\n drawText(page, \"Achievement levels (CDC Annex-4 style)\", MARGIN, y, bold, 10);\n y -= 14;\n let x = MARGIN;\n for (const level of LEVEL_ORDER) {\n const color = LEVEL_COLORS[level];\n page.drawRectangle({\n x,\n y: y - 2,\n width: 8,\n height: 8,\n color,\n });\n drawText(page, level, x + 12, y - 1, regular, 8, color);\n x += level.length * 8 * 0.52 + 28;\n }\n return y - 22;\n}\n\nfunction drawTableHeader(\n page: PDFPage,\n y: number,\n variant: TranscriptReportVariant,\n bold: PDFFont\n): number {\n const gradeCol = variant === \"level\" ? \"Level\" : \"Grade\";\n page.drawRectangle({\n x: MARGIN,\n y: y - 4,\n width: CONTENT_WIDTH,\n height: 18,\n color: rgb(0.94, 0.95, 0.97),\n });\n drawText(page, \"Subject / Class\", MARGIN + 6, y, bold, 8);\n drawText(page, \"Section\", MARGIN + 250, y, bold, 8);\n drawText(page, \"Score\", MARGIN + 320, y, bold, 8);\n drawText(page, gradeCol, MARGIN + 390, y, bold, 8);\n return y - 20;\n}\n\nfunction drawClassRow(\n page: PDFPage,\n y: number,\n row: TranscriptClassRow,\n variant: TranscriptReportVariant,\n regular: PDFFont,\n bold: PDFFont\n): number {\n const label = truncateToWidth(\n row.subject?.trim()\n ? `${row.subject} — ${row.className}`\n : row.className,\n regular,\n 9,\n 230\n );\n drawText(page, label, MARGIN + 6, y, regular, 9);\n drawText(\n page,\n truncateToWidth(row.section || \"—\", regular, 9, 60),\n MARGIN + 250,\n y,\n regular,\n 9\n );\n drawText(page, formatPercent(row.percentage), MARGIN + 320, y, regular, 9);\n\n const grade = row.gradeLabel?.trim() || \"—\";\n const levelKey = LEVEL_ORDER.find(\n (l) => l.toLowerCase() === grade.toLowerCase()\n );\n const gradeColor =\n variant === \"level\" && levelKey\n ? LEVEL_COLORS[levelKey]\n : rgb(0.12, 0.12, 0.14);\n drawText(\n page,\n truncateToWidth(grade, bold, 9, 120),\n MARGIN + 390,\n y,\n bold,\n 9,\n gradeColor\n );\n\n y -= 14;\n\n if (row.categorySummaries.length > 0) {\n const cats = row.categorySummaries\n .filter((c) => c.average !== null)\n .map((c) => `${c.name}: ${formatPercent(c.average)}`)\n .join(\" · \");\n if (cats) {\n drawText(\n page,\n truncateToWidth(cats, regular, 7, CONTENT_WIDTH - 12),\n MARGIN + 6,\n y,\n regular,\n 7,\n rgb(0.45, 0.45, 0.5)\n );\n y -= 12;\n }\n }\n\n page.drawLine({\n start: { x: MARGIN, y: y + 4 },\n end: { x: PAGE_WIDTH - MARGIN, y: y + 4 },\n thickness: 0.5,\n color: rgb(0.9, 0.9, 0.92),\n });\n return y - 6;\n}\n"],"names":[],"mappings":"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","debug_id":"1384d4a1-636c-51e0-8dbd-e4271dae1aac"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export declare function listAcademicYears(schoolId: string): import("@prisma/client/runtime/library.js").PrismaPromise<({
|
|
2
|
+
terms: {
|
|
3
|
+
id: string;
|
|
4
|
+
createdAt: Date;
|
|
5
|
+
updatedAt: Date;
|
|
6
|
+
name: string;
|
|
7
|
+
startDate: Date;
|
|
8
|
+
endDate: Date;
|
|
9
|
+
isCurrent: boolean;
|
|
10
|
+
academicYearId: string;
|
|
11
|
+
}[];
|
|
12
|
+
} & {
|
|
13
|
+
id: string;
|
|
14
|
+
createdAt: Date;
|
|
15
|
+
updatedAt: Date;
|
|
16
|
+
name: string;
|
|
17
|
+
schoolId: string;
|
|
18
|
+
startDate: Date;
|
|
19
|
+
endDate: Date;
|
|
20
|
+
isCurrent: boolean;
|
|
21
|
+
})[]>;
|
|
22
|
+
export declare function getAcademicYearById(id: string, schoolId: string): import("@prisma/client/runtime/library.js").DynamicModelExtensionFluentApi<import(".prisma/client").Prisma.TypeMap<import("@prisma/client/runtime/library.js").InternalArgs & {
|
|
23
|
+
result: {
|
|
24
|
+
userProfile: {
|
|
25
|
+
profilePicture: () => {
|
|
26
|
+
needs: {
|
|
27
|
+
profilePicture: true;
|
|
28
|
+
};
|
|
29
|
+
compute(profile: {
|
|
30
|
+
profilePicture: string | null;
|
|
31
|
+
}): string | null;
|
|
32
|
+
};
|
|
33
|
+
profilePictureThumbnail: () => {
|
|
34
|
+
needs: {
|
|
35
|
+
profilePictureThumbnail: true;
|
|
36
|
+
};
|
|
37
|
+
compute(profile: {
|
|
38
|
+
profilePictureThumbnail: string | null;
|
|
39
|
+
}): string | null;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
model: {};
|
|
44
|
+
query: {};
|
|
45
|
+
client: {};
|
|
46
|
+
}, {}>, "AcademicYear", "findFirst", null> & import("@prisma/client/runtime/library.js").PrismaPromise<({
|
|
47
|
+
terms: {
|
|
48
|
+
id: string;
|
|
49
|
+
createdAt: Date;
|
|
50
|
+
updatedAt: Date;
|
|
51
|
+
name: string;
|
|
52
|
+
startDate: Date;
|
|
53
|
+
endDate: Date;
|
|
54
|
+
isCurrent: boolean;
|
|
55
|
+
academicYearId: string;
|
|
56
|
+
}[];
|
|
57
|
+
} & {
|
|
58
|
+
id: string;
|
|
59
|
+
createdAt: Date;
|
|
60
|
+
updatedAt: Date;
|
|
61
|
+
name: string;
|
|
62
|
+
schoolId: string;
|
|
63
|
+
startDate: Date;
|
|
64
|
+
endDate: Date;
|
|
65
|
+
isCurrent: boolean;
|
|
66
|
+
}) | null>;
|
|
67
|
+
export declare function createAcademicYear(data: {
|
|
68
|
+
schoolId: string;
|
|
69
|
+
name: string;
|
|
70
|
+
startDate: Date;
|
|
71
|
+
endDate: Date;
|
|
72
|
+
isCurrent?: boolean;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
terms: {
|
|
75
|
+
id: string;
|
|
76
|
+
createdAt: Date;
|
|
77
|
+
updatedAt: Date;
|
|
78
|
+
name: string;
|
|
79
|
+
startDate: Date;
|
|
80
|
+
endDate: Date;
|
|
81
|
+
isCurrent: boolean;
|
|
82
|
+
academicYearId: string;
|
|
83
|
+
}[];
|
|
84
|
+
} & {
|
|
85
|
+
id: string;
|
|
86
|
+
createdAt: Date;
|
|
87
|
+
updatedAt: Date;
|
|
88
|
+
name: string;
|
|
89
|
+
schoolId: string;
|
|
90
|
+
startDate: Date;
|
|
91
|
+
endDate: Date;
|
|
92
|
+
isCurrent: boolean;
|
|
93
|
+
}>;
|
|
94
|
+
export declare function updateAcademicYear(id: string, schoolId: string, data: {
|
|
95
|
+
name?: string;
|
|
96
|
+
startDate?: Date;
|
|
97
|
+
endDate?: Date;
|
|
98
|
+
isCurrent?: boolean;
|
|
99
|
+
}): Promise<({
|
|
100
|
+
terms: {
|
|
101
|
+
id: string;
|
|
102
|
+
createdAt: Date;
|
|
103
|
+
updatedAt: Date;
|
|
104
|
+
name: string;
|
|
105
|
+
startDate: Date;
|
|
106
|
+
endDate: Date;
|
|
107
|
+
isCurrent: boolean;
|
|
108
|
+
academicYearId: string;
|
|
109
|
+
}[];
|
|
110
|
+
} & {
|
|
111
|
+
id: string;
|
|
112
|
+
createdAt: Date;
|
|
113
|
+
updatedAt: Date;
|
|
114
|
+
name: string;
|
|
115
|
+
schoolId: string;
|
|
116
|
+
startDate: Date;
|
|
117
|
+
endDate: Date;
|
|
118
|
+
isCurrent: boolean;
|
|
119
|
+
}) | null>;
|
|
120
|
+
export declare function deleteAcademicYear(id: string, schoolId: string): import("@prisma/client/runtime/library.js").PrismaPromise<import("@prisma/client/runtime/library.js").GetBatchResult>;
|
|
121
|
+
export declare function createAcademicTerm(data: {
|
|
122
|
+
academicYearId: string;
|
|
123
|
+
name: string;
|
|
124
|
+
startDate: Date;
|
|
125
|
+
endDate: Date;
|
|
126
|
+
isCurrent?: boolean;
|
|
127
|
+
}): Promise<{
|
|
128
|
+
id: string;
|
|
129
|
+
createdAt: Date;
|
|
130
|
+
updatedAt: Date;
|
|
131
|
+
name: string;
|
|
132
|
+
startDate: Date;
|
|
133
|
+
endDate: Date;
|
|
134
|
+
isCurrent: boolean;
|
|
135
|
+
academicYearId: string;
|
|
136
|
+
}>;
|
|
137
|
+
export declare function updateAcademicTerm(id: string, academicYearId: string, data: {
|
|
138
|
+
name?: string;
|
|
139
|
+
startDate?: Date;
|
|
140
|
+
endDate?: Date;
|
|
141
|
+
isCurrent?: boolean;
|
|
142
|
+
}): Promise<{
|
|
143
|
+
id: string;
|
|
144
|
+
createdAt: Date;
|
|
145
|
+
updatedAt: Date;
|
|
146
|
+
name: string;
|
|
147
|
+
startDate: Date;
|
|
148
|
+
endDate: Date;
|
|
149
|
+
isCurrent: boolean;
|
|
150
|
+
academicYearId: string;
|
|
151
|
+
} | null>;
|
|
152
|
+
export declare function deleteAcademicTerm(id: string, academicYearId: string): import("@prisma/client/runtime/library.js").PrismaPromise<import("@prisma/client/runtime/library.js").GetBatchResult>;
|
|
153
|
+
export declare function getCurrentAcademicYear(schoolId: string): import("@prisma/client/runtime/library.js").DynamicModelExtensionFluentApi<import(".prisma/client").Prisma.TypeMap<import("@prisma/client/runtime/library.js").InternalArgs & {
|
|
154
|
+
result: {
|
|
155
|
+
userProfile: {
|
|
156
|
+
profilePicture: () => {
|
|
157
|
+
needs: {
|
|
158
|
+
profilePicture: true;
|
|
159
|
+
};
|
|
160
|
+
compute(profile: {
|
|
161
|
+
profilePicture: string | null;
|
|
162
|
+
}): string | null;
|
|
163
|
+
};
|
|
164
|
+
profilePictureThumbnail: () => {
|
|
165
|
+
needs: {
|
|
166
|
+
profilePictureThumbnail: true;
|
|
167
|
+
};
|
|
168
|
+
compute(profile: {
|
|
169
|
+
profilePictureThumbnail: string | null;
|
|
170
|
+
}): string | null;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
model: {};
|
|
175
|
+
query: {};
|
|
176
|
+
client: {};
|
|
177
|
+
}, {}>, "AcademicYear", "findFirst", null> & import("@prisma/client/runtime/library.js").PrismaPromise<({
|
|
178
|
+
terms: {
|
|
179
|
+
id: string;
|
|
180
|
+
createdAt: Date;
|
|
181
|
+
updatedAt: Date;
|
|
182
|
+
name: string;
|
|
183
|
+
startDate: Date;
|
|
184
|
+
endDate: Date;
|
|
185
|
+
isCurrent: boolean;
|
|
186
|
+
academicYearId: string;
|
|
187
|
+
}[];
|
|
188
|
+
} & {
|
|
189
|
+
id: string;
|
|
190
|
+
createdAt: Date;
|
|
191
|
+
updatedAt: Date;
|
|
192
|
+
name: string;
|
|
193
|
+
schoolId: string;
|
|
194
|
+
startDate: Date;
|
|
195
|
+
endDate: Date;
|
|
196
|
+
isCurrent: boolean;
|
|
197
|
+
}) | null>;
|
|
198
|
+
//# sourceMappingURL=academicCalendar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"academicCalendar.d.ts","sourceRoot":"/","sources":["models/academicCalendar.ts"],"names":[],"mappings":"AAKA,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;MAQjD;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAO/D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;;;;;;;;;;;;;;;;;;;;GAmBA;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;;;;;;;;;;;;;;;;;;;;WAmBF;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,yHAE9D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;;;;;;;;;GAUA;AAED,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,MAAM,EACV,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE;IACJ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;;;;;;;;;UAeF;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,yHAEpE;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAOtD"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Academic year / term model helpers (C1).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
!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]="e5645cac-bf80-59ad-8fd6-24a0b0f87bf7")}catch(e){}}();
|
|
6
|
+
import { prisma } from "../lib/prisma.js";
|
|
7
|
+
export function listAcademicYears(schoolId) {
|
|
8
|
+
return prisma.academicYear.findMany({
|
|
9
|
+
where: { schoolId },
|
|
10
|
+
orderBy: [{ isCurrent: "desc" }, { startDate: "desc" }],
|
|
11
|
+
include: {
|
|
12
|
+
terms: { orderBy: [{ isCurrent: "desc" }, { startDate: "asc" }] },
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export function getAcademicYearById(id, schoolId) {
|
|
17
|
+
return prisma.academicYear.findFirst({
|
|
18
|
+
where: { id, schoolId },
|
|
19
|
+
include: {
|
|
20
|
+
terms: { orderBy: [{ isCurrent: "desc" }, { startDate: "asc" }] },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export function createAcademicYear(data) {
|
|
25
|
+
return prisma.$transaction(async (tx) => {
|
|
26
|
+
if (data.isCurrent) {
|
|
27
|
+
await tx.academicYear.updateMany({
|
|
28
|
+
where: { schoolId: data.schoolId, isCurrent: true },
|
|
29
|
+
data: { isCurrent: false },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return tx.academicYear.create({
|
|
33
|
+
data: {
|
|
34
|
+
schoolId: data.schoolId,
|
|
35
|
+
name: data.name,
|
|
36
|
+
startDate: data.startDate,
|
|
37
|
+
endDate: data.endDate,
|
|
38
|
+
isCurrent: data.isCurrent ?? false,
|
|
39
|
+
},
|
|
40
|
+
include: { terms: true },
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function updateAcademicYear(id, schoolId, data) {
|
|
45
|
+
return prisma.$transaction(async (tx) => {
|
|
46
|
+
const existing = await tx.academicYear.findFirst({ where: { id, schoolId } });
|
|
47
|
+
if (!existing)
|
|
48
|
+
return null;
|
|
49
|
+
if (data.isCurrent) {
|
|
50
|
+
await tx.academicYear.updateMany({
|
|
51
|
+
where: { schoolId, isCurrent: true, NOT: { id } },
|
|
52
|
+
data: { isCurrent: false },
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return tx.academicYear.update({
|
|
56
|
+
where: { id },
|
|
57
|
+
data,
|
|
58
|
+
include: {
|
|
59
|
+
terms: { orderBy: [{ isCurrent: "desc" }, { startDate: "asc" }] },
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export function deleteAcademicYear(id, schoolId) {
|
|
65
|
+
return prisma.academicYear.deleteMany({ where: { id, schoolId } });
|
|
66
|
+
}
|
|
67
|
+
export function createAcademicTerm(data) {
|
|
68
|
+
return prisma.$transaction(async (tx) => {
|
|
69
|
+
if (data.isCurrent) {
|
|
70
|
+
await tx.academicTerm.updateMany({
|
|
71
|
+
where: { academicYearId: data.academicYearId, isCurrent: true },
|
|
72
|
+
data: { isCurrent: false },
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return tx.academicTerm.create({ data: { ...data, isCurrent: data.isCurrent ?? false } });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function updateAcademicTerm(id, academicYearId, data) {
|
|
79
|
+
return prisma.$transaction(async (tx) => {
|
|
80
|
+
const existing = await tx.academicTerm.findFirst({
|
|
81
|
+
where: { id, academicYearId },
|
|
82
|
+
});
|
|
83
|
+
if (!existing)
|
|
84
|
+
return null;
|
|
85
|
+
if (data.isCurrent) {
|
|
86
|
+
await tx.academicTerm.updateMany({
|
|
87
|
+
where: { academicYearId, isCurrent: true, NOT: { id } },
|
|
88
|
+
data: { isCurrent: false },
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return tx.academicTerm.update({ where: { id }, data });
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
export function deleteAcademicTerm(id, academicYearId) {
|
|
95
|
+
return prisma.academicTerm.deleteMany({ where: { id, academicYearId } });
|
|
96
|
+
}
|
|
97
|
+
export function getCurrentAcademicYear(schoolId) {
|
|
98
|
+
return prisma.academicYear.findFirst({
|
|
99
|
+
where: { schoolId, isCurrent: true },
|
|
100
|
+
include: {
|
|
101
|
+
terms: { orderBy: [{ isCurrent: "desc" }, { startDate: "asc" }] },
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=academicCalendar.js.map
|
|
106
|
+
//# debugId=e5645cac-bf80-59ad-8fd6-24a0b0f87bf7
|