| 1234567891011121314151617181920212223242526272829303132333435363738394041 |
- // Display-only statistics from completed lesson facts. Never transition an appointment
- // or infer a balance from attendance; reservations and financial ledgers remain independent.
- function studentLessonStatisticsSql() {
- return `WITH facts AS (
- SELECT l.*,COALESCE(NULLIF(NULLIF(l.yyds,'0'),''),'lesson:'||l."objectId") AS "classKey",
- COALESCE(r."actualStartAt",l."lessonAt",xiaoshu_safe_shanghai_timestamp(l.plpjsj),c."createdAt",l."sourceCreatedAt",l."createdAt") AS "actualAt",
- COALESCE(NULLIF(CASE WHEN r."durationMinutes">0 AND r."durationMinutes"::text NOT IN ('NaN','Infinity','-Infinity') THEN r."durationMinutes" END,0),
- CASE WHEN a."durationMinutes">0 AND a."durationMinutes"::text NOT IN ('NaN','Infinity','-Infinity') THEN a."durationMinutes" END,
- CASE WHEN to_jsonb(l)->>'durationMinutes' ~ '^\\d+(\\.\\d+)?$' THEN NULLIF((to_jsonb(l)->>'durationMinutes')::numeric,0) END,
- CASE l.kclx WHEN '1' THEN 30 WHEN '2' THEN 60 WHEN '3' THEN 60 END) AS minutes,
- COALESCE(NULLIF(l."courseCategoryKey",''),NULLIF(r."courseCategoryKey",''),a."courseCategoryKey",'') AS category
- FROM "LessonRecord" l
- LEFT JOIN LATERAL (SELECT * FROM "CommonModel" c WHERE c.company=l.company AND c."modelId"=59 AND c."itemId"=l.id ORDER BY c."objectId" LIMIT 1) c ON true
- LEFT JOIN LATERAL (SELECT r.* FROM "LessonCompletionReport" r WHERE r.company=l.company AND r."studentId"::text=l.xymz
- AND (r."lessonId"=l."objectId" OR r."appointmentGeneralId"::numeric=CASE WHEN l.yyds~'^\\d+$' THEN l.yyds::numeric END)
- ORDER BY r."submittedAt" DESC NULLS LAST,r."objectId" DESC LIMIT 1) r ON true
- LEFT JOIN LATERAL (SELECT a.* FROM "CommonModel" ac JOIN "CourseAppointment" a ON a.company=ac.company AND a.id=ac."itemId" AND a.szyh=l.xymz
- WHERE ac.company=l.company AND ac."modelId"=54 AND ac."generalId"=CASE WHEN l.yyds~'^\\d+$' THEN l.yyds::numeric END ORDER BY ac."objectId" LIMIT 1) a ON true
- WHERE l.company=$1 AND ($2='' OR l.xymz=$2) AND c.status IS DISTINCT FROM -2
- ), ranked AS (SELECT facts.*,row_number() OVER(PARTITION BY company,xymz,"classKey" ORDER BY "actualAt" DESC NULLS LAST,"objectId") rn FROM facts),
- lessons AS (SELECT * FROM ranked WHERE rn=1)
- SELECT xymz AS "studentId",COUNT(*)::int total,
- COUNT(*) FILTER(WHERE minutes=30 AND kclx IS DISTINCT FROM '3' AND category<>'trial')::int t30,
- COUNT(*) FILTER(WHERE minutes=60 AND kclx IS DISTINCT FROM '3' AND category<>'trial')::int t60,
- COUNT(*) FILTER(WHERE kclx='3' OR category='trial')::int tiyan,
- COUNT(*) FILTER(WHERE minutes IS NULL)::int "unknownDurationCount",
- CASE WHEN COUNT(*) FILTER(WHERE minutes IS NULL)>0 THEN NULL ELSE SUM(minutes) END AS "totalMinutes"
- FROM lessons GROUP BY xymz`;
- }
- function studentUnmatchedLessonsSql() {
- return `SELECT a.szyh AS "studentId",COUNT(*)::int total FROM "CourseAppointment" a
- LEFT JOIN LATERAL (SELECT * FROM "CommonModel" c WHERE c.company=a.company AND c."modelId"=54 AND c."itemId"=a.id ORDER BY c."objectId" LIMIT 1) c ON true
- WHERE a.company=$1 AND ($2='' OR a.szyh=$2) AND a.dszt ~ '^[0-9]+$' AND a.dszt::numeric>10 AND c.status IS DISTINCT FROM -2
- AND NOT EXISTS(SELECT 1 FROM "LessonRecord" l WHERE l.company=a.company AND l.xymz=a.szyh AND l.yyds=c."generalId"::text) GROUP BY a.szyh`;
- }
- async function studentLessonStatistics(companyId,studentId) {
- const [rows,missing]=await Promise.all([Psql.query(studentLessonStatisticsSql(),[companyId,String(studentId)]),Psql.query(studentUnmatchedLessonsSql(),[companyId,String(studentId)])]);
- const result=rows[0]||{total:0,t30:0,t60:0,tiyan:0,totalMinutes:0,unknownDurationCount:0};
- const unresolved=Number(missing[0]?.total)||0;
- return {...result,unknownDurationCount:Number(result.unknownDurationCount)+unresolved,totalMinutes:unresolved?null:result.totalMinutes};
- }
|