import { SessionProgressStatus, AssignmentReportStatus, UserAssignmentStatus } from '@prisma/client'; import { prisma } from '../../lib/prisma.js'; import { ForbiddenError, NotFoundError, ValidationError } from '../../shared/errors/index.js'; import { getHomeworkForSession, getHomeworkReportRequiredFor, type SessionHomework, } from './sessionHomework.js'; import { activateAssignmentsForSession, seedAssignmentDefinitions, } from '../assignments/assignments.service.js'; async function getCompletedOrders(userId: bigint): Promise { const rows = await prisma.sessionProgress.findMany({ where: { userId, status: SessionProgressStatus.completed }, select: { sessionOrder: true }, }); return rows.map((row) => row.sessionOrder).sort((a, b) => a - b); } function serializeReport(row: { sessionOrder: number; reportText: string; submittedAt: Date; }) { return { sessionOrder: row.sessionOrder, reportText: row.reportText, submittedAt: row.submittedAt.toISOString(), }; } export async function hasHomeworkReport(userId: bigint, sessionOrder: number): Promise { const row = await prisma.homeworkReport.findUnique({ where: { userId_sessionOrder: { userId, sessionOrder } }, select: { id: true }, }); return Boolean(row); } export async function getHomeworkReport(userId: bigint, sessionOrder: number) { const homework = getHomeworkForSession(sessionOrder); if (!homework) { throw new NotFoundError(`Session ${sessionOrder} has no homework`); } const row = await prisma.homeworkReport.findUnique({ where: { userId_sessionOrder: { userId, sessionOrder } }, }); return { sessionOrder, homework, report: row ? serializeReport(row) : null, }; } export async function submitHomeworkReport(userId: bigint, sessionOrder: number, reportText: string) { const homework = getHomeworkForSession(sessionOrder); if (!homework) { throw new ValidationError(`Session ${sessionOrder} has no homework to report`); } const trimmed = reportText.trim(); if (trimmed.length < 10) { throw new ValidationError('گزارش تکلیف باید حداقل چند جمله باشد'); } const completedOrders = await getCompletedOrders(userId); if (!completedOrders.includes(sessionOrder)) { throw new ForbiddenError('Homework can only be reported after the session is completed'); } const row = await prisma.homeworkReport.upsert({ where: { userId_sessionOrder: { userId, sessionOrder } }, create: { userId, sessionOrder, reportText: trimmed, }, update: { reportText: trimmed, submittedAt: new Date(), }, }); // Bridge into cumulative assignment system (idempotent activation + append report). await seedAssignmentDefinitions(); await activateAssignmentsForSession(userId, sessionOrder); const userAssignment = await prisma.userAssignment.findFirst({ where: { userId, definition: { sessionOrder } }, orderBy: { activatedAt: 'asc' }, }); if (userAssignment && userAssignment.status !== UserAssignmentStatus.COMPLETED) { const now = new Date(); await prisma.assignmentReport.create({ data: { userAssignmentId: userAssignment.id, userId, reportText: trimmed, reportStatus: AssignmentReportStatus.completed, }, }); await prisma.userAssignment.update({ where: { id: userAssignment.id }, data: { status: UserAssignmentStatus.REPORTED, startedAt: userAssignment.startedAt ?? now, lastActivityAt: now, }, }); } return { sessionOrder, homework, report: serializeReport(row), }; } export type PendingHomeworkReport = { forSessionOrder: number; homeworkSessionOrder: number; homework: SessionHomework; submitted: boolean; }; /** * If the learner's next educational session requires a prior homework report, * return the pending homework info. Null when nothing is required or already submitted. */ export async function getPendingHomeworkReport( userId: bigint, nextSessionOrder: number, ): Promise { const homeworkSessionOrder = getHomeworkReportRequiredFor(nextSessionOrder); if (!homeworkSessionOrder) { return null; } const homework = getHomeworkForSession(homeworkSessionOrder); if (!homework) { return null; } const completedOrders = await getCompletedOrders(userId); if (!completedOrders.includes(homeworkSessionOrder)) { return null; } const submitted = await hasHomeworkReport(userId, homeworkSessionOrder); return { forSessionOrder: nextSessionOrder, homeworkSessionOrder, homework, submitted, }; } /** * Soft check-in only: assignments never block the next session * (`blocksNextSession` is false). Kept for call-site compatibility. */ export async function assertHomeworkReportForSessionStart( _userId: bigint, _sessionOrder: number, ): Promise { // Intentionally no-op — cumulative homework must not gate session unlock. }