@things-factory/learning 10.1.260 → 10.1.262

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.
Files changed (103) hide show
  1. package/dist-client/components/learning-material-form.d.ts +41 -0
  2. package/dist-client/components/learning-material-form.js +202 -0
  3. package/dist-client/components/learning-material-form.js.map +1 -0
  4. package/dist-client/components/learning-unit-form.d.ts +3 -0
  5. package/dist-client/components/learning-unit-form.js +10 -2
  6. package/dist-client/components/learning-unit-form.js.map +1 -1
  7. package/dist-client/pages/learning-course-editor.d.ts +1 -0
  8. package/dist-client/pages/learning-course-editor.js +19 -6
  9. package/dist-client/pages/learning-course-editor.js.map +1 -1
  10. package/dist-client/pages/learning-course-list.d.ts +1 -5
  11. package/dist-client/pages/learning-course-list.js +54 -16
  12. package/dist-client/pages/learning-course-list.js.map +1 -1
  13. package/dist-client/pages/learning-enrollment-status.d.ts +31 -16
  14. package/dist-client/pages/learning-enrollment-status.js +104 -117
  15. package/dist-client/pages/learning-enrollment-status.js.map +1 -1
  16. package/dist-client/pages/learning-view.d.ts +11 -0
  17. package/dist-client/pages/learning-view.js +94 -19
  18. package/dist-client/pages/learning-view.js.map +1 -1
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/dist-client/utils/learning-api.d.ts +48 -3
  21. package/dist-client/utils/learning-api.js +51 -2
  22. package/dist-client/utils/learning-api.js.map +1 -1
  23. package/dist-client/utils/material-source.d.ts +12 -0
  24. package/dist-client/utils/material-source.js +77 -0
  25. package/dist-client/utils/material-source.js.map +1 -0
  26. package/dist-server/ai-reads.js +1 -1
  27. package/dist-server/ai-reads.js.map +1 -1
  28. package/dist-server/service/course/course-mutation.d.ts +1 -1
  29. package/dist-server/service/course/course-mutation.js +7 -6
  30. package/dist-server/service/course/course-mutation.js.map +1 -1
  31. package/dist-server/service/course/course-query.d.ts +2 -0
  32. package/dist-server/service/course/course-query.js +27 -3
  33. package/dist-server/service/course/course-query.js.map +1 -1
  34. package/dist-server/service/course/course-rules.d.ts +3 -1
  35. package/dist-server/service/course/course-rules.js +5 -1
  36. package/dist-server/service/course/course-rules.js.map +1 -1
  37. package/dist-server/service/course/course-service.d.ts +5 -4
  38. package/dist-server/service/course/course-service.js +10 -14
  39. package/dist-server/service/course/course-service.js.map +1 -1
  40. package/dist-server/service/course/course-type.d.ts +1 -0
  41. package/dist-server/service/course/course-type.js +4 -0
  42. package/dist-server/service/course/course-type.js.map +1 -1
  43. package/dist-server/service/course/course-unit.d.ts +3 -2
  44. package/dist-server/service/course/course-unit.js +7 -2
  45. package/dist-server/service/course/course-unit.js.map +1 -1
  46. package/dist-server/service/course/index.d.ts +2 -1
  47. package/dist-server/service/course/index.js +2 -1
  48. package/dist-server/service/course/index.js.map +1 -1
  49. package/dist-server/service/course/material-audio.d.ts +1 -0
  50. package/dist-server/service/course/material-audio.js +43 -0
  51. package/dist-server/service/course/material-audio.js.map +1 -0
  52. package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
  53. package/dist-server/service/course/material-draft-mutation.js +55 -0
  54. package/dist-server/service/course/material-draft-mutation.js.map +1 -0
  55. package/dist-server/service/course/material-draft-rules.d.ts +38 -0
  56. package/dist-server/service/course/material-draft-rules.js +83 -0
  57. package/dist-server/service/course/material-draft-rules.js.map +1 -0
  58. package/dist-server/service/course/material-draft-service.d.ts +15 -0
  59. package/dist-server/service/course/material-draft-service.js +162 -0
  60. package/dist-server/service/course/material-draft-service.js.map +1 -0
  61. package/dist-server/service/enrollment/enrollment-mutation.d.ts +1 -1
  62. package/dist-server/service/enrollment/enrollment-mutation.js +6 -6
  63. package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -1
  64. package/dist-server/service/enrollment/enrollment-query.d.ts +8 -4
  65. package/dist-server/service/enrollment/enrollment-query.js +47 -36
  66. package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
  67. package/dist-server/service/enrollment/enrollment-service.d.ts +4 -3
  68. package/dist-server/service/enrollment/enrollment-service.js +41 -18
  69. package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
  70. package/dist-server/service/enrollment/enrollment-status.d.ts +42 -18
  71. package/dist-server/service/enrollment/enrollment-status.js +143 -42
  72. package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
  73. package/dist-server/service/enrollment/enrollment-type.d.ts +2 -8
  74. package/dist-server/service/enrollment/enrollment-type.js +20 -42
  75. package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
  76. package/dist-server/service/enrollment/enrollment.d.ts +1 -0
  77. package/dist-server/service/enrollment/enrollment.js +5 -0
  78. package/dist-server/service/enrollment/enrollment.js.map +1 -1
  79. package/dist-server/service/enrollment/index.d.ts +3 -3
  80. package/dist-server/service/enrollment/index.js +3 -3
  81. package/dist-server/service/enrollment/index.js.map +1 -1
  82. package/dist-server/service/enrollment/learning-ai.d.ts +9 -2
  83. package/dist-server/service/enrollment/learning-ai.js +25 -28
  84. package/dist-server/service/enrollment/learning-ai.js.map +1 -1
  85. package/dist-server/service/enrollment/learning-documents.js +3 -4
  86. package/dist-server/service/enrollment/learning-documents.js.map +1 -1
  87. package/dist-server/service/enrollment/progress-rules.d.ts +40 -11
  88. package/dist-server/service/enrollment/progress-rules.js +54 -13
  89. package/dist-server/service/enrollment/progress-rules.js.map +1 -1
  90. package/dist-server/service/enrollment/unit-progress.d.ts +4 -0
  91. package/dist-server/service/enrollment/unit-progress.js +20 -0
  92. package/dist-server/service/enrollment/unit-progress.js.map +1 -1
  93. package/dist-server/service/index.d.ts +2 -2
  94. package/dist-server/service/index.js +2 -2
  95. package/dist-server/service/index.js.map +1 -1
  96. package/dist-server/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +14 -13
  98. package/translations/en.json +42 -9
  99. package/translations/es.json +42 -9
  100. package/translations/ja.json +42 -9
  101. package/translations/ko.json +42 -9
  102. package/translations/ms.json +42 -9
  103. package/translations/zh.json +42 -9
@@ -1 +1 @@
1
- {"version":3,"file":"enrollment-mutation.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Int, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { ScalarObject, SpeakRefusals } from '@things-factory/shell'\n\nimport { Enrollment } from './enrollment'\nimport { acknowledgeEnrollment, cancelEnrollment, DIRECT_SOURCE_KIND, enroll, learnersOf, recordTick, startQuizAttempt, submitQuizAttempt } from './enrollment-service'\nimport { remindEnrollments } from './enrollment-remind'\nimport { LearningAudienceInput, LearningTickResult, QuizAttemptView, QuizResultView } from './enrollment-type'\n\n\n/*\n * The writes of enrolments (ADR-0194 decisions 4-8): a learner's ticks, acknowledgement and quiz (`learning` mutation,\n * the task `learning.taking`, on their own enrolments only); a manager's assigning, cancelling and reminding (`course`\n * mutation, `learning.managing`).\n */\n@Resolver(Enrollment)\n@UseMiddleware(SpeakRefusals)\nexport class EnrollmentMutation {\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => LearningTickResult, {\n description: 'To record a tick of a unit looked at - the server counts the time since the last tick (15 seconds at most) and refuses a video position ahead of what was watched.'\n })\n async recordLearningTick(\n @Arg('enrollmentId') enrollmentId: string,\n @Arg('unitId') unitId: string,\n @Arg('seconds', type => Int, { description: 'Seconds looked at since the last tick, as the screen says - it may lower what is counted, never raise it.' }) seconds: number,\n @Arg('position', type => Int, { description: 'Where the video stands, in seconds - 0 for a document.' }) position: number,\n @Ctx() context: ResolverContext\n ): Promise<LearningTickResult> {\n return await recordTick(enrollmentId, unitId, seconds, position, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Enrollment, { description: \"To confirm the course's acknowledgement.\" })\n async acknowledgeEnrollment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await acknowledgeEnrollment(id, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => QuizAttemptView, { description: 'To start a quiz attempt - the questions and choices drawn and shuffled, never the answers.' })\n async startQuizAttempt(@Arg('enrollmentId') enrollmentId: string, @Ctx() context: ResolverContext): Promise<QuizAttemptView> {\n const { attempt, questions } = await startQuizAttempt(enrollmentId, context)\n return { attemptId: attempt.id, questions }\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => QuizResultView, { description: \"To send an attempt's answers - scored on the server; the score and whether it passed come back, never the right answers.\" })\n async submitQuizAttempt(\n @Arg('attemptId') attemptId: string,\n @Arg('answers', type => ScalarObject, { description: 'The choice ids picked, by question id.' }) answers: Record<string, string[]>,\n @Ctx() context: ResolverContext\n ): Promise<QuizResultView> {\n return await submitQuizAttempt(attemptId, answers, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [Enrollment], { description: 'To assign a course to an audience - the people it names now - to be completed by a day; each gets a to-do.' })\n async assignCourse(\n @Arg('courseId') courseId: string,\n @Arg('audiences', type => [LearningAudienceInput]) audiences: LearningAudienceInput[],\n @Arg('dueOn', { nullable: true, description: 'YYYY-MM-DD.' }) dueOn: string,\n @Ctx() context: ResolverContext\n ): Promise<Enrollment[]> {\n return await enroll({ kind: DIRECT_SOURCE_KIND, id: '' }, await learnersOf(audiences, context), courseId, dueOn, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Enrollment, { description: 'To cancel an enrolment, with the reason - a completed one stays on record as cancelled, and whoever asked for it is told.' })\n async cancelEnrollment(@Arg('id') id: string, @Arg('reason') reason: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await cancelEnrollment(id, reason, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Int, { description: 'To remind the learners of open enrolments - answers how many were reminded.' })\n async remindEnrollments(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext): Promise<number> {\n return await remindEnrollments(ids, context)\n }\n}\n"]}
1
+ {"version":3,"file":"enrollment-mutation.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Int, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { ScalarObject, SpeakRefusals } from '@things-factory/shell'\n\nimport { Enrollment } from './enrollment'\nimport { acknowledgeEnrollment, cancelEnrollments, DIRECT_SOURCE_KIND, enroll, learnersOf, recordTick, startQuizAttempt, submitQuizAttempt } from './enrollment-service'\nimport { remindEnrollments } from './enrollment-remind'\nimport { LearningAudienceInput, LearningTickResult, QuizAttemptView, QuizResultView } from './enrollment-type'\n\n\n/*\n * The writes of enrolments (ADR-0194 decisions 4-8): a learner's ticks, acknowledgement and quiz (`learning` mutation,\n * the task `learning.taking`, on their own enrolments only); a manager's assigning, cancelling and reminding (`course`\n * mutation, `learning.managing`).\n */\n@Resolver(Enrollment)\n@UseMiddleware(SpeakRefusals)\nexport class EnrollmentMutation {\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => LearningTickResult, {\n description: 'To record a tick of a unit looked at - the server counts the time since the last tick (15 seconds at most) and refuses a video position ahead of what was watched.'\n })\n async recordLearningTick(\n @Arg('enrollmentId') enrollmentId: string,\n @Arg('unitId') unitId: string,\n @Arg('seconds', type => Int, { description: 'Seconds looked at since the last tick, as the screen says - it may lower what is counted, never raise it.' }) seconds: number,\n @Arg('position', type => Int, { description: 'Where the video stands, in seconds - 0 for a document.' }) position: number,\n @Ctx() context: ResolverContext\n ): Promise<LearningTickResult> {\n return await recordTick(enrollmentId, unitId, seconds, position, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Enrollment, { description: \"To confirm the course's acknowledgement.\" })\n async acknowledgeEnrollment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await acknowledgeEnrollment(id, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => QuizAttemptView, { description: 'To start a quiz attempt - the questions and choices drawn and shuffled, never the answers.' })\n async startQuizAttempt(@Arg('enrollmentId') enrollmentId: string, @Ctx() context: ResolverContext): Promise<QuizAttemptView> {\n const { attempt, questions } = await startQuizAttempt(enrollmentId, context)\n return { attemptId: attempt.id, questions }\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => QuizResultView, { description: \"To send an attempt's answers - scored on the server; the score and whether it passed come back, never the right answers.\" })\n async submitQuizAttempt(\n @Arg('attemptId') attemptId: string,\n @Arg('answers', type => ScalarObject, { description: 'The choice ids picked, by question id.' }) answers: Record<string, string[]>,\n @Ctx() context: ResolverContext\n ): Promise<QuizResultView> {\n return await submitQuizAttempt(attemptId, answers, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [Enrollment], { description: 'To assign a course to an audience - the people it names now - to be completed by a day; each gets a to-do.' })\n async assignCourse(\n @Arg('courseId') courseId: string,\n @Arg('audiences', type => [LearningAudienceInput]) audiences: LearningAudienceInput[],\n @Arg('dueOn', { nullable: true, description: 'YYYY-MM-DD.' }) dueOn: string,\n @Ctx() context: ResolverContext\n ): Promise<Enrollment[]> {\n return await enroll({ kind: DIRECT_SOURCE_KIND, id: '' }, await learnersOf(audiences, context), courseId, dueOn, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [Enrollment], { description: 'To cancel enrolments with one reason, together - one refused, none is cancelled. A completed one stays on record as cancelled, and whoever asked for it is told.' })\n async cancelEnrollments(@Arg('ids', type => [String]) ids: string[], @Arg('reason') reason: string, @Ctx() context: ResolverContext): Promise<Enrollment[]> {\n return await cancelEnrollments(ids, reason, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Int, { description: 'To remind the learners of open enrolments - answers how many were reminded.' })\n async remindEnrollments(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext): Promise<number> {\n return await remindEnrollments(ids, context)\n }\n}\n"]}
@@ -1,14 +1,15 @@
1
1
  import { User } from '@things-factory/auth-base';
2
- import { ListParam } from '@things-factory/shell';
2
+ import { ListParam, ListValueCount } from '@things-factory/shell';
3
3
  import { Course } from '../course/course';
4
4
  import { CourseVersion } from '../course/course-version';
5
5
  import { Enrollment } from './enrollment';
6
- import { EnrollmentStatusFilter, EnrollmentStatusList, EnrollmentStatusRow, LearningContent, LearningEnrollmentList, LearningUnit } from './enrollment-type';
6
+ import { CourseEnrollmentCounts, EnrollmentStatusList, EnrollmentStatusRow, LearningContent, LearningEnrollmentList, LearningUnit } from './enrollment-type';
7
7
  export declare class EnrollmentQuery {
8
8
  myEnrollments(context: ResolverContext): Promise<Enrollment[]>;
9
9
  myEnrollment(id: string, context: ResolverContext): Promise<Enrollment>;
10
- teamEnrollmentStatus(filter: EnrollmentStatusFilter, context: ResolverContext): Promise<EnrollmentStatusList>;
11
- enrollmentStatus(filter: EnrollmentStatusFilter, context: ResolverContext): Promise<EnrollmentStatusList>;
10
+ teamEnrollmentStatus(params: ListParam, context: ResolverContext): Promise<EnrollmentStatusList>;
11
+ enrollmentStatus(params: ListParam, context: ResolverContext): Promise<EnrollmentStatusList>;
12
+ enrollmentStateCounts(params: ListParam, context: ResolverContext): Promise<ListValueCount[]>;
12
13
  completionRegister(courseId: string, context: ResolverContext): Promise<EnrollmentStatusRow[]>;
13
14
  learningEnrollments(params: ListParam, context: ResolverContext): Promise<LearningEnrollmentList>;
14
15
  learningContent(enrollmentId: string, versionId: string, context: ResolverContext): Promise<LearningContent>;
@@ -21,3 +22,6 @@ export declare class EnrollmentQuery {
21
22
  attemptsLeft(enrollment: Enrollment, context: ResolverContext): Promise<number | null>;
22
23
  quizSize(enrollment: Enrollment, context: ResolverContext): Promise<number>;
23
24
  }
25
+ export declare class CourseEnrollmentFields {
26
+ enrollmentCounts(course: Course, context: ResolverContext): Promise<CourseEnrollmentCounts | null>;
27
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EnrollmentQuery = void 0;
3
+ exports.CourseEnrollmentFields = exports.EnrollmentQuery = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
6
  const auth_base_1 = require("@things-factory/auth-base");
@@ -16,22 +16,10 @@ const enrollment_type_1 = require("./enrollment-type");
16
16
  const learning_ai_1 = require("./learning-ai");
17
17
  const progress_rules_1 = require("./progress-rules");
18
18
  const rowOut = (row) => ({ id: row.enrollment.id, ...row });
19
- function listOf(rows) {
20
- const live = rows.filter(one => one.state !== 'CANCELLED');
21
- return {
22
- items: rows.map(rowOut),
23
- summary: {
24
- total: live.length,
25
- assigned: live.filter(one => one.enrollment.status === 'ASSIGNED').length,
26
- inProgress: live.filter(one => one.enrollment.status === 'IN_PROGRESS').length,
27
- completed: live.filter(one => one.state === 'COMPLETED').length,
28
- overdue: live.filter(one => one.state === 'OVERDUE').length
29
- }
30
- };
31
- }
19
+ const pageOf = ({ rows, total }) => ({ items: rows.map(rowOut), total });
32
20
  /** Whether the request reads how an enrolment went - its learner or a manager. */
33
21
  async function readsInside(enrollment, context) {
34
- return enrollment.userId === context.state.user.id || (await (0, course_service_1.holds)(context, 'course', 'query'));
22
+ return enrollment.userId === context.state.user.id || (await (0, auth_base_1.requestHolds)(context, 'course', 'query'));
35
23
  }
36
24
  /*
37
25
  * The reads of enrolments (ADR-0194 decisions 5-11): one's own (`learning` query, the task `learning.taking`); every
@@ -44,27 +32,21 @@ let EnrollmentQuery = class EnrollmentQuery {
44
32
  async myEnrollment(id, context) {
45
33
  return await (0, enrollment_service_1.myEnrollmentOf)(context, id);
46
34
  }
47
- async teamEnrollmentStatus(filter, context) {
48
- return listOf(await (0, enrollment_status_1.teamEnrollmentStatus)(filter ?? {}, context));
35
+ async teamEnrollmentStatus(params, context) {
36
+ return pageOf(await (0, enrollment_status_1.enrollmentPage)(params, await (0, enrollment_status_1.teamReachOf)(context), context));
37
+ }
38
+ async enrollmentStatus(params, context) {
39
+ return pageOf(await (0, enrollment_status_1.enrollmentPage)(params, { kind: 'all' }, context));
49
40
  }
50
- async enrollmentStatus(filter, context) {
51
- return listOf(await (0, enrollment_status_1.enrollmentStatus)(filter ?? {}, context));
41
+ async enrollmentStateCounts(params, context) {
42
+ const reach = (await (0, auth_base_1.requestHolds)(context, 'course', 'query')) ? { kind: 'all' } : await (0, enrollment_status_1.teamReachOf)(context);
43
+ return await (0, enrollment_status_1.enrollmentStateCounts)(params, reach, context);
52
44
  }
53
45
  async completionRegister(courseId, context) {
54
46
  return (await (0, enrollment_status_1.completionRegister)(courseId, context)).map(rowOut);
55
47
  }
56
48
  async learningEnrollments(params, context) {
57
- let rows = await (0, learning_ai_1.readableEnrollments)(context);
58
- for (const filter of params?.filters ?? []) {
59
- const values = Array.isArray(filter.value) ? filter.value.map(String) : [String(filter.value)];
60
- if (filter.name === 'state')
61
- rows = rows.filter(row => values.includes(row.state) !== (filter.operator === 'notin' || filter.operator === 'noteq'));
62
- }
63
- const search = String(params?.search ?? params?.filters?.find(one => one.operator === 'search')?.value ?? '').replace(/%/g, '').trim();
64
- if (search)
65
- rows = rows.filter(row => `${row.learner} ${row.departmentName ?? ''} ${row.course}`.includes(search));
66
- const { page = 1, limit = 0 } = (params?.pagination ?? {});
67
- return { items: limit > 0 ? rows.slice((page - 1) * limit, page * limit) : rows, total: rows.length };
49
+ return await (0, learning_ai_1.readableEnrollments)(params, context);
68
50
  }
69
51
  async learningContent(enrollmentId, versionId, context) {
70
52
  return await (0, learning_ai_1.courseContent)({ enrollmentId, versionId }, context);
@@ -132,22 +114,31 @@ tslib_1.__decorate([
132
114
  ], EnrollmentQuery.prototype, "myEnrollment", null);
133
115
  tslib_1.__decorate([
134
116
  (0, type_graphql_1.Directive)('@privilege(category: "learning", privilege: "query", domainOwnerGranted: true)'),
135
- (0, type_graphql_1.Query)(returns => enrollment_type_1.EnrollmentStatusList, { description: "Where the people of the request's departments stand - for a department head, status and days only." }),
136
- tslib_1.__param(0, (0, type_graphql_1.Arg)('filter', type => enrollment_type_1.EnrollmentStatusFilter, { nullable: true })),
117
+ (0, type_graphql_1.Query)(returns => enrollment_type_1.EnrollmentStatusList, { description: "Where the people of the request's departments stand - for a department head, status and days only. Filters: course, state, sourceKind, sourceId; search: the learner's name or the course's." }),
118
+ tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
137
119
  tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
138
120
  tslib_1.__metadata("design:type", Function),
139
- tslib_1.__metadata("design:paramtypes", [enrollment_type_1.EnrollmentStatusFilter, Object]),
121
+ tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
140
122
  tslib_1.__metadata("design:returntype", Promise)
141
123
  ], EnrollmentQuery.prototype, "teamEnrollmentStatus", null);
142
124
  tslib_1.__decorate([
143
125
  (0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "query", domainOwnerGranted: true)'),
144
- (0, type_graphql_1.Query)(returns => enrollment_type_1.EnrollmentStatusList, { description: 'Every enrolment of a course or a source, with its progress and evidence.' }),
145
- tslib_1.__param(0, (0, type_graphql_1.Arg)('filter', type => enrollment_type_1.EnrollmentStatusFilter, { nullable: true })),
126
+ (0, type_graphql_1.Query)(returns => enrollment_type_1.EnrollmentStatusList, { description: "Every enrolment, with its progress and evidence. Filters: course, state, sourceKind, sourceId; search: the learner's name or the course's." }),
127
+ tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
146
128
  tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
147
129
  tslib_1.__metadata("design:type", Function),
148
- tslib_1.__metadata("design:paramtypes", [enrollment_type_1.EnrollmentStatusFilter, Object]),
130
+ tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
149
131
  tslib_1.__metadata("design:returntype", Promise)
150
132
  ], EnrollmentQuery.prototype, "enrollmentStatus", null);
133
+ tslib_1.__decorate([
134
+ (0, type_graphql_1.Directive)('@privilege(category: "learning", privilege: "query", domainOwnerGranted: true)'),
135
+ (0, type_graphql_1.Query)(returns => [shell_1.ListValueCount], { description: "How many enrolments stand in each state under the list's other conditions, every state in order - of everyone for a manager, else of a department head's people (refused to anyone heading none), as the status list reads them." }),
136
+ tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
137
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
138
+ tslib_1.__metadata("design:type", Function),
139
+ tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
140
+ tslib_1.__metadata("design:returntype", Promise)
141
+ ], EnrollmentQuery.prototype, "enrollmentStateCounts", null);
151
142
  tslib_1.__decorate([
152
143
  (0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "query", domainOwnerGranted: true)'),
153
144
  (0, type_graphql_1.Query)(returns => [enrollment_type_1.EnrollmentStatusRow], { description: 'The completion register of a course - every completion by its day.' }),
@@ -242,4 +233,24 @@ exports.EnrollmentQuery = EnrollmentQuery = tslib_1.__decorate([
242
233
  (0, type_graphql_1.Resolver)(enrollment_1.Enrollment),
243
234
  (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
244
235
  ], EnrollmentQuery);
236
+ /* How a course's enrolments stand, on the course - for a manager (`course` query); nothing for anyone else. */
237
+ let CourseEnrollmentFields = class CourseEnrollmentFields {
238
+ async enrollmentCounts(course, context) {
239
+ if (!(await (0, auth_base_1.requestHolds)(context, 'course', 'query')))
240
+ return null;
241
+ return (await (0, enrollment_status_1.courseEnrollmentCounts)(context)).get(course.id) ?? { total: 0, assigned: 0, inProgress: 0, completed: 0, overdue: 0 };
242
+ }
243
+ };
244
+ exports.CourseEnrollmentFields = CourseEnrollmentFields;
245
+ tslib_1.__decorate([
246
+ (0, type_graphql_1.FieldResolver)(type => enrollment_type_1.CourseEnrollmentCounts, { nullable: true, description: "Where its enrolments stand - cancelled ones left out. For a manager; empty for anyone else." }),
247
+ tslib_1.__param(0, (0, type_graphql_1.Root)()),
248
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
249
+ tslib_1.__metadata("design:type", Function),
250
+ tslib_1.__metadata("design:paramtypes", [course_1.Course, Object]),
251
+ tslib_1.__metadata("design:returntype", Promise)
252
+ ], CourseEnrollmentFields.prototype, "enrollmentCounts", null);
253
+ exports.CourseEnrollmentFields = CourseEnrollmentFields = tslib_1.__decorate([
254
+ (0, type_graphql_1.Resolver)(course_1.Course)
255
+ ], CourseEnrollmentFields);
245
256
  //# sourceMappingURL=enrollment-query.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"enrollment-query.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Int, Query, Resolver, Root, UseMiddleware } from 'type-graphql'\n\nimport { User } from '@things-factory/auth-base'\nimport { getRepository, ListParam, SpeakRefusals } from '@things-factory/shell'\n\nimport { Course } from '../course/course'\nimport { CourseQuestion } from '../course/course-question'\nimport { CourseVersion } from '../course/course-version'\nimport { holds, learningManagerOf, unitsOf } from '../course/course-service'\nimport { Enrollment } from './enrollment'\nimport { missingFor, myEnrollmentOf, myEnrollments, progressesOf, shownVersionOf, todayIn } from './enrollment-service'\nimport { completionRegister, enrollmentStatus, StatusRow, teamEnrollmentStatus } from './enrollment-status'\nimport { EnrollmentStatusFilter, EnrollmentStatusList, EnrollmentStatusRow, LearningContent, LearningEnrollmentList, LearningUnit } from './enrollment-type'\nimport { courseContent, readableEnrollments } from './learning-ai'\nimport { counts, overdue } from './progress-rules'\n\n\nconst rowOut = (row: StatusRow): EnrollmentStatusRow => ({ id: row.enrollment.id, ...row })\n\nfunction listOf(rows: StatusRow[]): EnrollmentStatusList {\n const live = rows.filter(one => one.state !== 'CANCELLED')\n return {\n items: rows.map(rowOut),\n summary: {\n total: live.length,\n assigned: live.filter(one => one.enrollment.status === 'ASSIGNED').length,\n inProgress: live.filter(one => one.enrollment.status === 'IN_PROGRESS').length,\n completed: live.filter(one => one.state === 'COMPLETED').length,\n overdue: live.filter(one => one.state === 'OVERDUE').length\n }\n }\n}\n\n/** Whether the request reads how an enrolment went - its learner or a manager. */\nasync function readsInside(enrollment: Enrollment, context: any): Promise<boolean> {\n return enrollment.userId === context.state.user.id || (await holds(context, 'course', 'query'))\n}\n\n/*\n * The reads of enrolments (ADR-0194 decisions 5-11): one's own (`learning` query, the task `learning.taking`); every\n * enrolment and the completion register for a manager (`course` query); a department head's people, status only.\n */\n@Resolver(Enrollment)\n@UseMiddleware(SpeakRefusals)\nexport class EnrollmentQuery {\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [Enrollment], { description: \"The request's own enrolments - open ones first, the soonest due first, then the completed.\" })\n async myEnrollments(@Ctx() context: ResolverContext): Promise<Enrollment[]> {\n return await myEnrollments(context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Enrollment, { description: \"One of the request's own enrolments.\" })\n async myEnrollment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await myEnrollmentOf(context, id)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EnrollmentStatusList, { description: \"Where the people of the request's departments stand - for a department head, status and days only.\" })\n async teamEnrollmentStatus(@Arg('filter', type => EnrollmentStatusFilter, { nullable: true }) filter: EnrollmentStatusFilter, @Ctx() context: ResolverContext): Promise<EnrollmentStatusList> {\n return listOf(await teamEnrollmentStatus(filter ?? {}, context))\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EnrollmentStatusList, { description: 'Every enrolment of a course or a source, with its progress and evidence.' })\n async enrollmentStatus(@Arg('filter', type => EnrollmentStatusFilter, { nullable: true }) filter: EnrollmentStatusFilter, @Ctx() context: ResolverContext): Promise<EnrollmentStatusList> {\n return listOf(await enrollmentStatus(filter ?? {}, context))\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [EnrollmentStatusRow], { description: 'The completion register of a course - every completion by its day.' })\n async completionRegister(@Arg('courseId') courseId: string, @Ctx() context: ResolverContext): Promise<EnrollmentStatusRow[]> {\n return (await completionRegister(courseId, context)).map(rowOut)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => LearningEnrollmentList, { description: 'The enrolments the request may read - a manager every one, a department head their people (status only), anyone else their own - for the assistant (ADR-0194 decision 12).' })\n async learningEnrollments(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<LearningEnrollmentList> {\n let rows = await readableEnrollments(context)\n for (const filter of params?.filters ?? []) {\n const values = Array.isArray(filter.value) ? filter.value.map(String) : [String(filter.value)]\n if (filter.name === 'state') rows = rows.filter(row => values.includes(row.state) !== (filter.operator === 'notin' || filter.operator === 'noteq'))\n }\n const search = String(params?.search ?? params?.filters?.find(one => one.operator === 'search')?.value ?? '').replace(/%/g, '').trim()\n if (search) rows = rows.filter(row => `${row.learner} ${row.departmentName ?? ''} ${row.course}`.includes(search))\n const { page = 1, limit = 0 } = (params?.pagination ?? {}) as any\n return { items: limit > 0 ? rows.slice((page - 1) * limit, page * limit) : rows, total: rows.length }\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => LearningContent, { description: \"The units of one course version for the assistant - a learner's own enrolment's (not while a quiz attempt is open), or a version for a manager (ADR-0194 decision 12).\" })\n async learningContent(@Arg('enrollmentId', { nullable: true }) enrollmentId: string, @Arg('versionId', { nullable: true }) versionId: string, @Ctx() context: ResolverContext): Promise<LearningContent> {\n return await courseContent({ enrollmentId, versionId }, context)\n }\n\n /* ---------------------------------------------------------------- fields */\n\n @FieldResolver(type => Course, { nullable: true, description: 'The course.' })\n async course(@Root() enrollment: Enrollment): Promise<Course | null> {\n return enrollment.courseId ? await getRepository(Course).findOneBy({ id: enrollment.courseId }) : null\n }\n\n @FieldResolver(type => User, { nullable: true, description: 'Who takes it.' })\n async user(@Root() enrollment: Enrollment): Promise<User | null> {\n return enrollment.userId ? await getRepository(User).findOneBy({ id: enrollment.userId }) : null\n }\n\n @FieldResolver(type => CourseVersion, { nullable: true, description: 'The version it shows - its own once started, the published one before.' })\n async version(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<CourseVersion | null> {\n return await shownVersionOf(learningManagerOf(context), context.state.domain, enrollment)\n }\n\n @FieldResolver(type => String, { description: 'Where it stands today - OVERDUE for an open one past its due day.' })\n state(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): string {\n return overdue(enrollment, todayIn(context.state.domain)) ? 'OVERDUE' : enrollment.status\n }\n\n @FieldResolver(type => [LearningUnit], { description: 'Its units with how far the learner went - for its learner and managers.' })\n async units(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<LearningUnit[]> {\n if (!(await readsInside(enrollment, context))) return []\n const tx = learningManagerOf(context)\n const version = await shownVersionOf(tx, context.state.domain, enrollment)\n if (!version) return []\n const progress = await progressesOf(tx, enrollment.id)\n return (await unitsOf(tx, version.id)).map(unit => ({ unit, progress: progress.find(one => one.unitId === unit.id) ?? null, counts: counts(unit) }))\n }\n\n @FieldResolver(type => [String], { description: 'What still stands before completion - units, time, quiz, acknowledgement.' })\n async missing(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<string[]> {\n if (!(await readsInside(enrollment, context))) return []\n return await missingFor(learningManagerOf(context), context.state.domain, enrollment)\n }\n\n @FieldResolver(type => Int, { nullable: true, description: 'The quiz attempts left - no limit, or no quiz, when empty.' })\n async attemptsLeft(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<number | null> {\n if (!(await readsInside(enrollment, context))) return null\n const version = await shownVersionOf(learningManagerOf(context), context.state.domain, enrollment)\n return version?.attemptLimit != null ? Math.max(0, version.attemptLimit - (enrollment.attempts ?? 0)) : null\n }\n\n @FieldResolver(type => Int, { description: 'How many questions its quiz draws - 0 when it has none.' })\n async quizSize(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<number> {\n const tx = learningManagerOf(context)\n const version = await shownVersionOf(tx, context.state.domain, enrollment)\n if (!version || version.passScore == null) return 0\n const all = await tx.getRepository(CourseQuestion).count({ where: { version: { id: version.id } } as any })\n return version.questionCount ? Math.min(version.questionCount, all) : all\n }\n}\n"]}
1
+ {"version":3,"file":"enrollment-query.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Int, Query, Resolver, Root, UseMiddleware } from 'type-graphql'\n\nimport { requestHolds, User } from '@things-factory/auth-base'\nimport { getRepository, ListParam, ListValueCount, SpeakRefusals } from '@things-factory/shell'\n\nimport { Course } from '../course/course'\nimport { CourseQuestion } from '../course/course-question'\nimport { CourseVersion } from '../course/course-version'\nimport { learningManagerOf, unitsOf } from '../course/course-service'\nimport { Enrollment } from './enrollment'\nimport { missingFor, myEnrollmentOf, myEnrollments, progressesOf, shownVersionOf, todayIn } from './enrollment-service'\nimport { completionRegister, courseEnrollmentCounts, enrollmentPage, enrollmentStateCounts, StatusRow, teamReachOf } from './enrollment-status'\nimport { CourseEnrollmentCounts, EnrollmentStatusList, EnrollmentStatusRow, LearningContent, LearningEnrollmentList, LearningUnit } from './enrollment-type'\nimport { courseContent, readableEnrollments } from './learning-ai'\nimport { counts, overdue } from './progress-rules'\n\n\nconst rowOut = (row: StatusRow): EnrollmentStatusRow => ({ id: row.enrollment.id, ...row })\n\nconst pageOf = ({ rows, total }: { rows: StatusRow[]; total: number }): EnrollmentStatusList => ({ items: rows.map(rowOut), total })\n\n/** Whether the request reads how an enrolment went - its learner or a manager. */\nasync function readsInside(enrollment: Enrollment, context: any): Promise<boolean> {\n return enrollment.userId === context.state.user.id || (await requestHolds(context, 'course', 'query'))\n}\n\n/*\n * The reads of enrolments (ADR-0194 decisions 5-11): one's own (`learning` query, the task `learning.taking`); every\n * enrolment and the completion register for a manager (`course` query); a department head's people, status only.\n */\n@Resolver(Enrollment)\n@UseMiddleware(SpeakRefusals)\nexport class EnrollmentQuery {\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [Enrollment], { description: \"The request's own enrolments - open ones first, the soonest due first, then the completed.\" })\n async myEnrollments(@Ctx() context: ResolverContext): Promise<Enrollment[]> {\n return await myEnrollments(context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Enrollment, { description: \"One of the request's own enrolments.\" })\n async myEnrollment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await myEnrollmentOf(context, id)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EnrollmentStatusList, { description: \"Where the people of the request's departments stand - for a department head, status and days only. Filters: course, state, sourceKind, sourceId; search: the learner's name or the course's.\" })\n async teamEnrollmentStatus(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<EnrollmentStatusList> {\n return pageOf(await enrollmentPage(params, await teamReachOf(context), context))\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => EnrollmentStatusList, { description: \"Every enrolment, with its progress and evidence. Filters: course, state, sourceKind, sourceId; search: the learner's name or the course's.\" })\n async enrollmentStatus(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<EnrollmentStatusList> {\n return pageOf(await enrollmentPage(params, { kind: 'all' }, context))\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [ListValueCount], { description: \"How many enrolments stand in each state under the list's other conditions, every state in order - of everyone for a manager, else of a department head's people (refused to anyone heading none), as the status list reads them.\" })\n async enrollmentStateCounts(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<ListValueCount[]> {\n const reach = (await requestHolds(context, 'course', 'query')) ? ({ kind: 'all' } as const) : await teamReachOf(context)\n return await enrollmentStateCounts(params, reach, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => [EnrollmentStatusRow], { description: 'The completion register of a course - every completion by its day.' })\n async completionRegister(@Arg('courseId') courseId: string, @Ctx() context: ResolverContext): Promise<EnrollmentStatusRow[]> {\n return (await completionRegister(courseId, context)).map(rowOut)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => LearningEnrollmentList, { description: 'The enrolments the request may read - a manager every one, a department head their people (status only), anyone else their own - for the assistant (ADR-0194 decision 12).' })\n async learningEnrollments(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<LearningEnrollmentList> {\n return await readableEnrollments(params, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => LearningContent, { description: \"The units of one course version for the assistant - a learner's own enrolment's (not while a quiz attempt is open), or a version for a manager (ADR-0194 decision 12).\" })\n async learningContent(@Arg('enrollmentId', { nullable: true }) enrollmentId: string, @Arg('versionId', { nullable: true }) versionId: string, @Ctx() context: ResolverContext): Promise<LearningContent> {\n return await courseContent({ enrollmentId, versionId }, context)\n }\n\n /* ---------------------------------------------------------------- fields */\n\n @FieldResolver(type => Course, { nullable: true, description: 'The course.' })\n async course(@Root() enrollment: Enrollment): Promise<Course | null> {\n return enrollment.courseId ? await getRepository(Course).findOneBy({ id: enrollment.courseId }) : null\n }\n\n @FieldResolver(type => User, { nullable: true, description: 'Who takes it.' })\n async user(@Root() enrollment: Enrollment): Promise<User | null> {\n return enrollment.userId ? await getRepository(User).findOneBy({ id: enrollment.userId }) : null\n }\n\n @FieldResolver(type => CourseVersion, { nullable: true, description: 'The version it shows - its own once started, the published one before.' })\n async version(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<CourseVersion | null> {\n return await shownVersionOf(learningManagerOf(context), context.state.domain, enrollment)\n }\n\n @FieldResolver(type => String, { description: 'Where it stands today - OVERDUE for an open one past its due day.' })\n state(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): string {\n return overdue(enrollment, todayIn(context.state.domain)) ? 'OVERDUE' : enrollment.status\n }\n\n @FieldResolver(type => [LearningUnit], { description: 'Its units with how far the learner went - for its learner and managers.' })\n async units(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<LearningUnit[]> {\n if (!(await readsInside(enrollment, context))) return []\n const tx = learningManagerOf(context)\n const version = await shownVersionOf(tx, context.state.domain, enrollment)\n if (!version) return []\n const progress = await progressesOf(tx, enrollment.id)\n return (await unitsOf(tx, version.id)).map(unit => ({ unit, progress: progress.find(one => one.unitId === unit.id) ?? null, counts: counts(unit) }))\n }\n\n @FieldResolver(type => [String], { description: 'What still stands before completion - units, time, quiz, acknowledgement.' })\n async missing(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<string[]> {\n if (!(await readsInside(enrollment, context))) return []\n return await missingFor(learningManagerOf(context), context.state.domain, enrollment)\n }\n\n @FieldResolver(type => Int, { nullable: true, description: 'The quiz attempts left - no limit, or no quiz, when empty.' })\n async attemptsLeft(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<number | null> {\n if (!(await readsInside(enrollment, context))) return null\n const version = await shownVersionOf(learningManagerOf(context), context.state.domain, enrollment)\n return version?.attemptLimit != null ? Math.max(0, version.attemptLimit - (enrollment.attempts ?? 0)) : null\n }\n\n @FieldResolver(type => Int, { description: 'How many questions its quiz draws - 0 when it has none.' })\n async quizSize(@Root() enrollment: Enrollment, @Ctx() context: ResolverContext): Promise<number> {\n const tx = learningManagerOf(context)\n const version = await shownVersionOf(tx, context.state.domain, enrollment)\n if (!version || version.passScore == null) return 0\n const all = await tx.getRepository(CourseQuestion).count({ where: { version: { id: version.id } } as any })\n return version.questionCount ? Math.min(version.questionCount, all) : all\n }\n}\n\n/* How a course's enrolments stand, on the course - for a manager (`course` query); nothing for anyone else. */\n@Resolver(Course)\nexport class CourseEnrollmentFields {\n @FieldResolver(type => CourseEnrollmentCounts, { nullable: true, description: \"Where its enrolments stand - cancelled ones left out. For a manager; empty for anyone else.\" })\n async enrollmentCounts(@Root() course: Course, @Ctx() context: ResolverContext): Promise<CourseEnrollmentCounts | null> {\n if (!(await requestHolds(context, 'course', 'query'))) return null\n return (await courseEnrollmentCounts(context)).get(course.id) ?? { total: 0, assigned: 0, inProgress: 0, completed: 0, overdue: 0 }\n }\n}\n"]}
@@ -29,10 +29,11 @@ export declare function enroll(source: LearningSource, userIds: string[], course
29
29
  */
30
30
  export declare function withdraw(source: LearningSource, reason: string, context: any): Promise<number>;
31
31
  /**
32
- * A manager cancels an enrolment with a reason (decision 8). An open one is withdrawn; a completed one stays on record as
33
- * cancelled - never deleted - and whoever asked for it is told in the same transaction.
32
+ * A manager cancels enrolments with one reason (decision 8) - together: one refused, none is cancelled. An open one is
33
+ * withdrawn; a completed one stays on record as cancelled - never deleted - and whoever asked for it is told in the same
34
+ * transaction.
34
35
  */
35
- export declare function cancelEnrollment(id: string, reason: string, context: any): Promise<Enrollment>;
36
+ export declare function cancelEnrollments(ids: string[], reason: string, context: any): Promise<Enrollment[]>;
36
37
  /**
37
38
  * The people an audience names now - the domain, departments with those under them, roles and people, by organization's
38
39
  * one rule (ADR-0163). Each target must be of this domain.
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isOpen = exports.certificateNoOf = exports.todayIn = exports.learningAddressOf = exports.DIRECT_SOURCE_KIND = void 0;
4
4
  exports.enroll = enroll;
5
5
  exports.withdraw = withdraw;
6
- exports.cancelEnrollment = cancelEnrollment;
6
+ exports.cancelEnrollments = cancelEnrollments;
7
7
  exports.learnersOf = learnersOf;
8
8
  exports.enrollmentOf = enrollmentOf;
9
9
  exports.myEnrollmentOf = myEnrollmentOf;
@@ -50,13 +50,18 @@ const notFound = () => new shell_1.Refusal('learning-not-found', {}, 'the record
50
50
  const isOpen = (status) => enrollment_1.OPEN_ENROLLMENT_STATUSES.includes(status);
51
51
  exports.isOpen = isOpen;
52
52
  /* ---------------------------------------------------------------- the to-do (decision 5) */
53
- /** The to-do is issued as its own assignee: nobody else hands it out. worklist speaks its refusals through `t`. */
53
+ /** The context worklist closes a to-do under - `by` ends or aborts it; worklist speaks its refusals through `t`. */
54
54
  function todoContextOf(tx, domain, user) {
55
55
  const t = (0, translator_1.translatorFor)(user.locale || 'ko');
56
56
  return { state: { domain, user, tx }, t: (key, options) => t(key, options) };
57
57
  }
58
- /** Hands out 「<과정> 수강」 to the person, with the due day - nothing where the domain has not installed the to-do. */
59
- async function issueTodo(tx, domain, enrollment, course, person) {
58
+ /**
59
+ * Hands out 「<과정> 수강」 to the person, with the due day - nothing where the domain has not installed the to-do. worklist's
60
+ * handOut: the person's 할 일 and nobody's 상신함, also when the manager assigning it is the person (2026-11-08). The one
61
+ * who asked (`context`) stands as its issuer.
62
+ */
63
+ async function issueTodo(context, enrollment, course, person) {
64
+ const { domain, tx } = context.state;
60
65
  const activity = await tx.getRepository(worklist_1.Activity).findOne({ where: { domain: { id: domain.id }, name: activities_1.LEARNING_COURSE_ACTIVITY } });
61
66
  if (!activity) {
62
67
  env_1.logger.warn(`[learning] ${domain.subdomain}: the activity "${activities_1.LEARNING_COURSE_ACTIVITY}" is not installed - "${course.name}" not handed out`);
@@ -66,10 +71,8 @@ async function issueTodo(tx, domain, enrollment, course, person) {
66
71
  const name = t('text.learning-todo-name', { course: course.name });
67
72
  const headline = enrollment.dueOn ? t('text.learning-todo-headline', { dueOn: enrollment.dueOn }) : t('text.learning-todo-headline-no-due');
68
73
  const dueAt = enrollment.dueOn ? dueAtOf(enrollment.dueOn, domain.timezone) : undefined;
69
- const context = todoContextOf(tx, domain, person);
70
74
  const input = { enrollmentId: enrollment.id, headline, address: (0, exports.learningAddressOf)(enrollment.id) };
71
- const instance = await (0, worklist_1.issue)({ name, description: headline, activityId: activity.id, input, assignees: [], threadsMin: 0, dueAt }, context);
72
- await (0, worklist_1.assign)({ id: instance.id, assignees: [person.email], dueAt, reason: name }, context);
75
+ const instance = await (0, worklist_1.handOut)({ activityId: activity.id, name, description: headline, input, assignee: person, dueAt }, context);
73
76
  return instance.id;
74
77
  }
75
78
  /** The end of the due day in the domain's time zone. */
@@ -127,7 +130,7 @@ async function enroll(source, userIds, courseId, dueOn, context) {
127
130
  continue;
128
131
  }
129
132
  const saved = await repository.save({ domain, user: person, course, dueOn: dueOn || null, sourceKind: source.kind, sourceId, status: enrollment_1.EnrollmentStatus.ASSIGNED, learnedSeconds: 0, attempts: 0, creator: user, updater: user });
130
- const todo = await issueTodo(tx, domain, saved, course, person);
133
+ const todo = await issueTodo(context, saved, course, person);
131
134
  if (todo)
132
135
  await repository.update(saved.id, { todoInstanceId: todo });
133
136
  out.push((await repository.findOneBy({ id: saved.id })));
@@ -156,23 +159,33 @@ async function cancelOpen(tx, domain, row, by, reason) {
156
159
  return true;
157
160
  }
158
161
  /**
159
- * A manager cancels an enrolment with a reason (decision 8). An open one is withdrawn; a completed one stays on record as
160
- * cancelled - never deleted - and whoever asked for it is told in the same transaction.
162
+ * A manager cancels enrolments with one reason (decision 8) - together: one refused, none is cancelled. An open one is
163
+ * withdrawn; a completed one stays on record as cancelled - never deleted - and whoever asked for it is told in the same
164
+ * transaction.
161
165
  */
162
- async function cancelEnrollment(id, reason, context) {
163
- const { domain, user, tx } = context.state;
166
+ async function cancelEnrollments(ids, reason, context) {
164
167
  if (!reason?.trim())
165
168
  throw new shell_1.Refusal('learning-cancel-reason-required', {}, 'say why it is cancelled');
169
+ const unique = [...new Set(ids ?? [])];
170
+ if (!unique.length)
171
+ throw new shell_1.Refusal('learning-cancel-none', {}, 'pick the enrolments to cancel');
172
+ const out = [];
173
+ for (const id of unique)
174
+ out.push(await cancelOne(id, reason.trim(), context));
175
+ return out;
176
+ }
177
+ async function cancelOne(id, reason, context) {
178
+ const { domain, user, tx } = context.state;
166
179
  const row = await enrollmentOf(tx, domain, id);
167
180
  if (isOpen(row.status)) {
168
- if (!(await cancelOpen(tx, domain, row, user, reason.trim())))
181
+ if (!(await cancelOpen(tx, domain, row, user, reason)))
169
182
  throw new shell_1.Refusal('learning-enrollment-moved', {}, 'the enrolment changed meanwhile - reload and try again');
170
183
  }
171
184
  else if (row.status === enrollment_1.EnrollmentStatus.COMPLETED) {
172
- const claimed = await tx.getRepository(enrollment_1.Enrollment).update({ id: row.id, status: enrollment_1.EnrollmentStatus.COMPLETED }, { status: enrollment_1.EnrollmentStatus.CANCELLED, cancelledAt: new Date(), cancelReason: reason.trim(), updater: user });
185
+ const claimed = await tx.getRepository(enrollment_1.Enrollment).update({ id: row.id, status: enrollment_1.EnrollmentStatus.COMPLETED }, { status: enrollment_1.EnrollmentStatus.CANCELLED, cancelledAt: new Date(), cancelReason: reason, updater: user });
173
186
  if (!claimed.affected)
174
187
  throw new shell_1.Refusal('learning-enrollment-moved', {}, 'the enrolment changed meanwhile - reload and try again');
175
- await (0, learning_outcome_1.learningOutcomeOf)(row.sourceKind)?.cancelled?.((await enrollmentOf(tx, domain, id)), reason.trim(), context);
188
+ await (0, learning_outcome_1.learningOutcomeOf)(row.sourceKind)?.cancelled?.((await enrollmentOf(tx, domain, id)), reason, context);
176
189
  }
177
190
  else
178
191
  throw new shell_1.Refusal('learning-enrollment-cancelled', {}, 'the enrolment is cancelled already');
@@ -276,15 +289,22 @@ async function recordTick(enrollmentId, unitId, seconds, position, context, now
276
289
  throw new shell_1.Refusal('learning-link-not-counted', {}, 'a link is not counted');
277
290
  const repository = tx.getRepository(unit_progress_1.UnitProgress);
278
291
  const progress = (await repository.findOne({ where: { enrollment: { id: enrollment.id }, unit: { id: unit.id } } })) ??
279
- (await repository.save({ domain, enrollment, unit, learnedSeconds: 0, watchedSeconds: 0, positionSeconds: 0, creator: user, updater: user }));
292
+ (await repository.save({ domain, enrollment, unit, learnedSeconds: 0, watchedSeconds: 0, positionSeconds: 0, pagesSeen: 0, pagesRead: 0, pageSeconds: 0, pageAt: 0, creator: user, updater: user }));
280
293
  if (enrollment.status === enrollment_1.EnrollmentStatus.COMPLETED)
281
294
  return { enrollment, progress };
282
- const verdict = (0, progress_rules_1.tickVerdict)(progress, { seconds, position }, now, unit.kind === course_unit_1.CourseUnitKind.VIDEO);
283
- if ('refusal' in verdict)
295
+ const verdict = (0, progress_rules_1.tickVerdict)(progress, { seconds, position }, now, unit);
296
+ if ('refusal' in verdict) {
297
+ if (verdict.refusal === 'page-ahead')
298
+ throw new shell_1.Refusal('learning-page-ahead', { allowed: verdict.allowed }, `a page cannot be ahead of the one after the last read (${verdict.allowed})`);
284
299
  throw new shell_1.Refusal('learning-position-ahead', { allowed: verdict.allowed }, `the video cannot be ahead of what was watched (${verdict.allowed} s)`);
300
+ }
285
301
  progress.learnedSeconds += verdict.add;
286
302
  progress.watchedSeconds = verdict.watchedSeconds;
287
303
  progress.positionSeconds = verdict.positionSeconds;
304
+ progress.pagesSeen = verdict.pagesSeen;
305
+ progress.pagesRead = verdict.pagesRead;
306
+ progress.pageSeconds = verdict.pageSeconds;
307
+ progress.pageAt = verdict.pageAt;
288
308
  progress.lastTickAt = now;
289
309
  if (!progress.doneAt && (0, progress_rules_1.unitDone)(unit, progress))
290
310
  progress.doneAt = now;
@@ -292,6 +312,9 @@ async function recordTick(enrollmentId, unitId, seconds, position, context, now
292
312
  await repository.save(progress);
293
313
  if (verdict.add)
294
314
  await tx.getRepository(enrollment_1.Enrollment).increment({ id: enrollment.id }, 'learnedSeconds', verdict.add);
315
+ /* Where the learner was - the course opens on this unit again (2026-11-08). */
316
+ if (enrollment.lastUnitId !== unit.id)
317
+ await tx.getRepository(enrollment_1.Enrollment).update({ id: enrollment.id }, { lastUnitId: unit.id });
295
318
  enrollment = await completeIfMet(tx, domain, await enrollmentOf(tx, domain, enrollment.id), context);
296
319
  return { enrollment, progress };
297
320
  }