Naalya Handbook

Reports

The term report card — remarks and attendance around the gradebook, one per enrollment per term, published to families with a shareable slug.

A report is the term report card: the remarks and attendance summary wrapped around a student's grades for one term. One exists per (enrollmentId, termId) — a DB unique constraint — and its lifecycle is a single boolean: isPublished (+ publishedAt). No status enum, no approval chain.

The entity — and what it is not

report carries enrollmentId, termId, campusAcademicYearId, a unique shareable slug, four remark fields (overallRemarks, conductRemarks, classTeacherRemarks, headTeacherRemarks), an attendance jsonb ({ present, absent, total }), isPublished/publishedAt, and generatedBy.

The report is not an aggregator

create persists exactly what the caller sends — it does not pull grades or compute attendance. Grades stay in the grade module and are surfaced beside the report by the frontend; attendance arrives pre-summed in the DTO. There's also no PDF renderer here — the output is JSON, and the slug is the shareable identifier.

Scoping note: the entity is not @TenantScoped — isolation comes from the required campusAcademicYearId plus CASL permission scope on every access (the same belt-and-suspenders as campus_term).

Creating and publishing

create runs the familiar year-scoped guard chain: the enrollment must exist and its year must equal the payload's, then assertNotLocked and assertTermMatchesYear, then a unique slug is minted and the report saves unpublished.

  • publish / unpublish toggle visibility to families ("visible to guardians/students") and stamp/clear publishedAt.
  • Every mutation — update, publish, unpublish, remove — re-loads the enrollment and gates on the report's own year being unlocked; a locked year freezes its report cards along with everything else.
  • All routes check Resource.ACADEMIC_REPORT per action (CREATE/READ/UPDATE/DELETE), and list reads merge the caller's CASL scope plus the required campusAcademicYearId filter.

Audit snapshots exclude the content

reportSnapshot records metadata only — id, enrollment, term, slug, publish state. Remarks, attendance, and anything grade-shaped never enter the audit trail, matching the lesson-planning allowlist pattern.

Where to go next

On this page