@things-factory/learning 10.1.271 → 10.1.272

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.
@@ -1,7 +1,7 @@
1
1
  export declare const LEARNING_CERTIFICATE_KIND = "learning.certificate";
2
2
  export declare const LEARNING_REGISTER_KIND = "learning.completion-register";
3
3
  /** The certificate's template in code - a domain's own `PDFTemplate` of the kind prints in its place. */
4
- export declare const CERTIFICATE_TEMPLATE = "<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n@page { size: A4; margin: 0 }\nbody { margin: 0; font-family: \"Apple SD Gothic Neo\", \"Malgun Gothic\", \"Noto Sans KR\", sans-serif; color: #222 }\n.page { position: relative; box-sizing: border-box; width: 210mm; min-height: 297mm; padding: 30mm 24mm }\n.number { position: absolute; top: 16mm; left: 24mm; font-size: 9.5pt; color: #666 }\nh1 { margin: 6mm 0 16mm; text-align: center; font-size: 26pt; letter-spacing: 12pt }\ntable { width: 100%; border-collapse: collapse; font-size: 11.5pt }\nth, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-align: middle }\nth { background: #f2f2f2; font-weight: 600; text-align: center }\n.statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }\n.date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }\n.issuer { margin: 20mm auto 0; width: max-content; max-width: 100%; font-size: 14pt; line-height: 2; text-align: left }\n.issuer > div { padding-left: 3.5em; text-indent: -3.5em }\n.issuer .stamp { position: relative; display: inline-block; margin-left: 3mm; text-indent: 0 }\n.issuer .stamp img { position: absolute; left: 50%; top: 50%; width: 22mm; transform: translate(-50%, -50%); opacity: .9 }\nth { width: 32mm }\n</style></head><body><div class=\"page\">\n<div class=\"number\">\uBC1C\uAE09\uBC88\uD638 {{issue.number}}</div>\n<h1>\uC218\uB8CC\uC99D</h1>\n<table>\n<tr><th>\uC131\uBA85</th><td>{{learner.name}}</td></tr>\n<tr><th>\uACFC\uC815</th><td>{{course.name}} (\uAC1C\uC815\uBCF8 {{course.version}})</td></tr>\n<tr><th>\uC774\uC218\uC77C</th><td>{{completion.completedOn}}</td></tr>\n<tr><th>\uD559\uC2B5 \uC2DC\uAC04</th><td>{{completion.minutes}}\uBD84</td></tr>\n{{#if completion.score}}<tr><th>\uD3C9\uAC00 \uC810\uC218</th><td>{{completion.score}}\uC810</td></tr>{{/if}}\n<tr><th>\uC218\uB8CC \uBC88\uD638</th><td>{{completion.certificateNo}}</td></tr>\n</table>\n<p class=\"statement\">\uC704 \uC0AC\uB78C\uC740 \uC704 \uACFC\uC815\uC744 \uC774\uC218\uD558\uC600\uC74C\uC744 \uC99D\uBA85\uD569\uB2C8\uB2E4.</p>\n<div class=\"date\">{{issue.issuedOn}}</div>\n<div class=\"issuer\">\n<div>\uC0C1\uD638 : {{company.name}}</div>\n{{#if company.address}}<div>\uC8FC\uC18C : {{company.address}}</div>{{/if}}\n<div>\uB300\uD45C\uC774\uC0AC : {{company.representative}}<span class=\"stamp\">(\uC778){{#if company.seal}}<img src=\"{{company.seal}}\" alt=\"\">{{/if}}</span></div>\n</div>\n</div></body></html>";
4
+ export declare const CERTIFICATE_TEMPLATE: string;
5
5
  /** The completion register's template - one row per completion. */
6
- export declare const REGISTER_TEMPLATE = "<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n@page { size: A4; margin: 0 }\nbody { margin: 0; font-family: \"Apple SD Gothic Neo\", \"Malgun Gothic\", \"Noto Sans KR\", sans-serif; color: #222 }\n.page { position: relative; box-sizing: border-box; width: 210mm; min-height: 297mm; padding: 30mm 24mm }\n.number { position: absolute; top: 16mm; left: 24mm; font-size: 9.5pt; color: #666 }\nh1 { margin: 6mm 0 16mm; text-align: center; font-size: 26pt; letter-spacing: 12pt }\ntable { width: 100%; border-collapse: collapse; font-size: 11.5pt }\nth, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-align: middle }\nth { background: #f2f2f2; font-weight: 600; text-align: center }\n.statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }\n.date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }\n.issuer { margin: 20mm auto 0; width: max-content; max-width: 100%; font-size: 14pt; line-height: 2; text-align: left }\n.issuer > div { padding-left: 3.5em; text-indent: -3.5em }\n.issuer .stamp { position: relative; display: inline-block; margin-left: 3mm; text-indent: 0 }\n.issuer .stamp img { position: absolute; left: 50%; top: 50%; width: 22mm; transform: translate(-50%, -50%); opacity: .9 }\n.page { padding: 24mm 16mm }\nh1 { font-size: 20pt; letter-spacing: 4pt; margin-bottom: 8mm }\n.meta { margin-bottom: 6mm; font-size: 10.5pt }\ntable { font-size: 9.5pt }\nth, td { padding: 2mm 2.4mm }\n</style></head><body><div class=\"page\">\n<div class=\"number\">\uBC1C\uAE09\uBC88\uD638 {{issue.number}}</div>\n<h1>\uC774\uC218 \uB300\uC7A5</h1>\n<div class=\"meta\">\uACFC\uC815 : {{course.name}} \u00B7 \uAE30\uC900\uC77C : {{issue.asOf}} \u00B7 \uC774\uC218\uC790 {{count}}\uBA85</div>\n<table>\n<tr><th>\uBC88\uD638</th><th>\uC131\uBA85</th><th>\uBD80\uC11C</th><th>\uAC1C\uC815\uBCF8</th><th>\uC774\uC218\uC77C</th><th>\uD559\uC2B5 \uC2DC\uAC04(\uBD84)</th><th>\uC810\uC218</th><th>\uC218\uB8CC \uBC88\uD638</th></tr>\n{{#each rows}}<tr><td>{{this.no}}</td><td>{{this.name}}</td><td>{{this.department}}</td><td>{{this.version}}</td><td>{{this.completedOn}}</td><td>{{this.minutes}}</td><td>{{this.score}}</td><td>{{this.certificateNo}}</td></tr>{{/each}}\n</table>\n<div class=\"date\">{{issue.issuedOn}}</div>\n<div class=\"issuer\">\n<div>\uC0C1\uD638 : {{company.name}}</div>\n{{#if company.address}}<div>\uC8FC\uC18C : {{company.address}}</div>{{/if}}\n<div>\uB300\uD45C\uC774\uC0AC : {{company.representative}}<span class=\"stamp\">(\uC778){{#if company.seal}}<img src=\"{{company.seal}}\" alt=\"\">{{/if}}</span></div>\n</div>\n</div></body></html>";
6
+ export declare const REGISTER_TEMPLATE: string;
7
7
  export declare function registerLearningDocuments(): void;
@@ -26,15 +26,9 @@ th, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-
26
26
  th { background: #f2f2f2; font-weight: 600; text-align: center }
27
27
  .statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }
28
28
  .date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }
29
- .issuer { margin: 20mm auto 0; width: max-content; max-width: 100%; font-size: 14pt; line-height: 2; text-align: left }
30
- .issuer > div { padding-left: 3.5em; text-indent: -3.5em }
31
- .issuer .stamp { position: relative; display: inline-block; margin-left: 3mm; text-indent: 0 }
32
- .issuer .stamp img { position: absolute; left: 50%; top: 50%; width: 22mm; transform: translate(-50%, -50%); opacity: .9 }`;
33
- const ISSUER = `<div class="issuer">
34
- <div>상호 : {{company.name}}</div>
35
- {{#if company.address}}<div>주소 : {{company.address}}</div>{{/if}}
36
- <div>대표이사 : {{company.representative}}<span class="stamp">(인){{#if company.seal}}<img src="{{company.seal}}" alt="">{{/if}}</span></div>
37
- </div>`;
29
+ ${pdf_1.ISSUER_STYLE}`;
30
+ /* the issuer and its seal, as every issued document prints them (pdf `issuerBlock`) - no registration number here */
31
+ const ISSUER = (0, pdf_1.issuerBlock)({ registrationNumber: false });
38
32
  /** The certificate's template in code - a domain's own `PDFTemplate` of the kind prints in its place. */
39
33
  exports.CERTIFICATE_TEMPLATE = `<!doctype html><html lang="ko"><head><meta charset="utf-8"><style>
40
34
  ${STYLE}
@@ -1 +1 @@
1
- {"version":3,"file":"learning-documents.js","sourceRoot":"","sources":["../../../server/service/enrollment/learning-documents.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The documents this module issues through the pdf module (ADR-0194 decisions 9, 10 · ADR-0176): the certificate of a completed\n * enrolment (`learning.certificate`) and the completion register of a course (`learning.completion-register`). The pdf module\n * numbers and prints them with 「우리 회사」 - the company's name, representative and seal as the application keeps them.\n */\nimport { requestHolds, User } from '@things-factory/auth-base'\nimport { registerIssuedDocumentKind } from '@things-factory/pdf'\nimport { getRepository, Refusal } from '@things-factory/shell'\n\nimport { Course } from '../course/course'\nimport { CourseVersion } from '../course/course-version'\nimport { Enrollment, EnrollmentStatus } from './enrollment'\nimport { completionRegister } from './enrollment-status'\n\nexport const LEARNING_CERTIFICATE_KIND = 'learning.certificate'\nexport const LEARNING_REGISTER_KIND = 'learning.completion-register'\n\nconst STYLE = `@page { size: A4; margin: 0 }\nbody { margin: 0; font-family: \"Apple SD Gothic Neo\", \"Malgun Gothic\", \"Noto Sans KR\", sans-serif; color: #222 }\n.page { position: relative; box-sizing: border-box; width: 210mm; min-height: 297mm; padding: 30mm 24mm }\n.number { position: absolute; top: 16mm; left: 24mm; font-size: 9.5pt; color: #666 }\nh1 { margin: 6mm 0 16mm; text-align: center; font-size: 26pt; letter-spacing: 12pt }\ntable { width: 100%; border-collapse: collapse; font-size: 11.5pt }\nth, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-align: middle }\nth { background: #f2f2f2; font-weight: 600; text-align: center }\n.statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }\n.date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }\n.issuer { margin: 20mm auto 0; width: max-content; max-width: 100%; font-size: 14pt; line-height: 2; text-align: left }\n.issuer > div { padding-left: 3.5em; text-indent: -3.5em }\n.issuer .stamp { position: relative; display: inline-block; margin-left: 3mm; text-indent: 0 }\n.issuer .stamp img { position: absolute; left: 50%; top: 50%; width: 22mm; transform: translate(-50%, -50%); opacity: .9 }`\n\nconst ISSUER = `<div class=\"issuer\">\n<div>상호 : {{company.name}}</div>\n{{#if company.address}}<div>주소 : {{company.address}}</div>{{/if}}\n<div>대표이사 : {{company.representative}}<span class=\"stamp\">(인){{#if company.seal}}<img src=\"{{company.seal}}\" alt=\"\">{{/if}}</span></div>\n</div>`\n\n/** The certificate's template in code - a domain's own `PDFTemplate` of the kind prints in its place. */\nexport const CERTIFICATE_TEMPLATE = `<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n${STYLE}\nth { width: 32mm }\n</style></head><body><div class=\"page\">\n<div class=\"number\">발급번호 {{issue.number}}</div>\n<h1>수료증</h1>\n<table>\n<tr><th>성명</th><td>{{learner.name}}</td></tr>\n<tr><th>과정</th><td>{{course.name}} (개정본 {{course.version}})</td></tr>\n<tr><th>이수일</th><td>{{completion.completedOn}}</td></tr>\n<tr><th>학습 시간</th><td>{{completion.minutes}}분</td></tr>\n{{#if completion.score}}<tr><th>평가 점수</th><td>{{completion.score}}점</td></tr>{{/if}}\n<tr><th>수료 번호</th><td>{{completion.certificateNo}}</td></tr>\n</table>\n<p class=\"statement\">위 사람은 위 과정을 이수하였음을 증명합니다.</p>\n<div class=\"date\">{{issue.issuedOn}}</div>\n${ISSUER}\n</div></body></html>`\n\n/** The completion register's template - one row per completion. */\nexport const REGISTER_TEMPLATE = `<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n${STYLE}\n.page { padding: 24mm 16mm }\nh1 { font-size: 20pt; letter-spacing: 4pt; margin-bottom: 8mm }\n.meta { margin-bottom: 6mm; font-size: 10.5pt }\ntable { font-size: 9.5pt }\nth, td { padding: 2mm 2.4mm }\n</style></head><body><div class=\"page\">\n<div class=\"number\">발급번호 {{issue.number}}</div>\n<h1>이수 대장</h1>\n<div class=\"meta\">과정 : {{course.name}} · 기준일 : {{issue.asOf}} · 이수자 {{count}}명</div>\n<table>\n<tr><th>번호</th><th>성명</th><th>부서</th><th>개정본</th><th>이수일</th><th>학습 시간(분)</th><th>점수</th><th>수료 번호</th></tr>\n{{#each rows}}<tr><td>{{this.no}}</td><td>{{this.name}}</td><td>{{this.department}}</td><td>{{this.version}}</td><td>{{this.completedOn}}</td><td>{{this.minutes}}</td><td>{{this.score}}</td><td>{{this.certificateNo}}</td></tr>{{/each}}\n</table>\n<div class=\"date\">{{issue.issuedOn}}</div>\n${ISSUER}\n</div></body></html>`\n\nconst dayOf = (at: Date | string | null | undefined, timezone?: string | null) =>\n at ? new Intl.DateTimeFormat('en-CA', { timeZone: timezone || 'UTC', year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(at)) : ''\n\nasync function enrollmentFor(context: any, id: string): Promise<Enrollment | null> {\n return id ? await getRepository(Enrollment, context.state.tx).findOne({ where: { id, domain: { id: context.state.domain.id } } as any }) : null\n}\n\n/** A certificate is issued and read by the person it is of, once completed, and by a manager. */\nasync function mayHaveCertificate(context: any, id: string | null | undefined): Promise<boolean> {\n const enrollment = await enrollmentFor(context, id ?? '')\n if (!enrollment || enrollment.status !== EnrollmentStatus.COMPLETED) return false\n return enrollment.userId === context.state.user?.id || (await requestHolds(context, 'course', 'query'))\n}\n\nexport function registerLearningDocuments(): void {\n registerIssuedDocumentKind({\n kind: LEARNING_CERTIFICATE_KIND,\n prefix: '수료',\n refType: 'Enrollment',\n template: { content_template: CERTIFICATE_TEMPLATE, page_size: 'A4' },\n async dataOf(context, refId) {\n const enrollment = await enrollmentFor(context, refId)\n if (!enrollment || enrollment.status !== EnrollmentStatus.COMPLETED) throw new Refusal('learning-not-completed', {}, 'a certificate is issued for a completed enrolment')\n const tx = context.state.tx\n const course = await getRepository(Course, tx).findOneBy({ id: enrollment.courseId! })\n const version = await getRepository(CourseVersion, tx).findOne({ where: { id: enrollment.versionId! } as any, withDeleted: true })\n const learner = await getRepository(User, tx).findOneBy({ id: enrollment.userId! })\n return {\n subjectName: `${learner?.name ?? ''} · ${course?.name ?? ''}`,\n data: {\n learner: { name: learner?.name ?? '' },\n course: { name: course?.name ?? '', version: version?.number ?? '' },\n completion: {\n completedOn: dayOf(enrollment.completedAt, context.state.domain.timezone),\n minutes: Math.floor((enrollment.learnedSeconds ?? 0) / 60),\n score: enrollment.score ?? null,\n certificateNo: enrollment.certificateNo ?? ''\n }\n }\n }\n },\n async mayIssue(context, refId) {\n return await mayHaveCertificate(context, refId)\n },\n async mayRead(context, row) {\n return await mayHaveCertificate(context, row.refId)\n }\n })\n\n registerIssuedDocumentKind({\n kind: LEARNING_REGISTER_KIND,\n prefix: '이수대장',\n refType: 'Course',\n asOf: true,\n template: { content_template: REGISTER_TEMPLATE, page_size: 'A4' },\n async dataOf(context, refId, input) {\n const course = await getRepository(Course, context.state.tx).findOne({ where: { id: refId, domain: { id: context.state.domain.id } } as any })\n if (!course) throw new Refusal('learning-not-found', {}, 'the record was not found. reload and try again.')\n const timezone = context.state.domain.timezone\n const rows = (await completionRegister(course.id, context)).filter(row => !input.asOf || dayOf(row.enrollment.completedAt, timezone) <= input.asOf)\n return {\n subjectName: course.name,\n data: {\n course: { name: course.name },\n count: rows.length,\n rows: rows.map((row, index) => ({\n no: index + 1,\n name: row.user?.name ?? '',\n department: row.departmentName ?? '',\n version: row.versionNumber ?? '',\n completedOn: dayOf(row.enrollment.completedAt, timezone),\n minutes: Math.floor((row.enrollment.learnedSeconds ?? 0) / 60),\n score: row.enrollment.score ?? '',\n certificateNo: row.enrollment.certificateNo ?? ''\n }))\n }\n }\n },\n async mayIssue(context) {\n return await requestHolds(context, 'course', 'query')\n },\n async mayRead(context) {\n return await requestHolds(context, 'course', 'query')\n }\n })\n}\n"]}
1
+ {"version":3,"file":"learning-documents.js","sourceRoot":"","sources":["../../../server/service/enrollment/learning-documents.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The documents this module issues through the pdf module (ADR-0194 decisions 9, 10 · ADR-0176): the certificate of a completed\n * enrolment (`learning.certificate`) and the completion register of a course (`learning.completion-register`). The pdf module\n * numbers and prints them with 「우리 회사」 - the company's name, representative and seal as the application keeps them.\n */\nimport { requestHolds, User } from '@things-factory/auth-base'\nimport { issuerBlock, ISSUER_STYLE, registerIssuedDocumentKind } from '@things-factory/pdf'\nimport { getRepository, Refusal } from '@things-factory/shell'\n\nimport { Course } from '../course/course'\nimport { CourseVersion } from '../course/course-version'\nimport { Enrollment, EnrollmentStatus } from './enrollment'\nimport { completionRegister } from './enrollment-status'\n\nexport const LEARNING_CERTIFICATE_KIND = 'learning.certificate'\nexport const LEARNING_REGISTER_KIND = 'learning.completion-register'\n\nconst STYLE = `@page { size: A4; margin: 0 }\nbody { margin: 0; font-family: \"Apple SD Gothic Neo\", \"Malgun Gothic\", \"Noto Sans KR\", sans-serif; color: #222 }\n.page { position: relative; box-sizing: border-box; width: 210mm; min-height: 297mm; padding: 30mm 24mm }\n.number { position: absolute; top: 16mm; left: 24mm; font-size: 9.5pt; color: #666 }\nh1 { margin: 6mm 0 16mm; text-align: center; font-size: 26pt; letter-spacing: 12pt }\ntable { width: 100%; border-collapse: collapse; font-size: 11.5pt }\nth, td { border: 1px solid #444; padding: 3.2mm 4mm; text-align: left; vertical-align: middle }\nth { background: #f2f2f2; font-weight: 600; text-align: center }\n.statement { margin: 22mm 0 0; text-align: center; font-size: 13pt; line-height: 2 }\n.date { margin-top: 18mm; text-align: center; font-size: 13pt; letter-spacing: 1pt }\n${ISSUER_STYLE}`\n\n/* the issuer and its seal, as every issued document prints them (pdf `issuerBlock`) - no registration number here */\nconst ISSUER = issuerBlock({ registrationNumber: false })\n\n/** The certificate's template in code - a domain's own `PDFTemplate` of the kind prints in its place. */\nexport const CERTIFICATE_TEMPLATE = `<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n${STYLE}\nth { width: 32mm }\n</style></head><body><div class=\"page\">\n<div class=\"number\">발급번호 {{issue.number}}</div>\n<h1>수료증</h1>\n<table>\n<tr><th>성명</th><td>{{learner.name}}</td></tr>\n<tr><th>과정</th><td>{{course.name}} (개정본 {{course.version}})</td></tr>\n<tr><th>이수일</th><td>{{completion.completedOn}}</td></tr>\n<tr><th>학습 시간</th><td>{{completion.minutes}}분</td></tr>\n{{#if completion.score}}<tr><th>평가 점수</th><td>{{completion.score}}점</td></tr>{{/if}}\n<tr><th>수료 번호</th><td>{{completion.certificateNo}}</td></tr>\n</table>\n<p class=\"statement\">위 사람은 위 과정을 이수하였음을 증명합니다.</p>\n<div class=\"date\">{{issue.issuedOn}}</div>\n${ISSUER}\n</div></body></html>`\n\n/** The completion register's template - one row per completion. */\nexport const REGISTER_TEMPLATE = `<!doctype html><html lang=\"ko\"><head><meta charset=\"utf-8\"><style>\n${STYLE}\n.page { padding: 24mm 16mm }\nh1 { font-size: 20pt; letter-spacing: 4pt; margin-bottom: 8mm }\n.meta { margin-bottom: 6mm; font-size: 10.5pt }\ntable { font-size: 9.5pt }\nth, td { padding: 2mm 2.4mm }\n</style></head><body><div class=\"page\">\n<div class=\"number\">발급번호 {{issue.number}}</div>\n<h1>이수 대장</h1>\n<div class=\"meta\">과정 : {{course.name}} · 기준일 : {{issue.asOf}} · 이수자 {{count}}명</div>\n<table>\n<tr><th>번호</th><th>성명</th><th>부서</th><th>개정본</th><th>이수일</th><th>학습 시간(분)</th><th>점수</th><th>수료 번호</th></tr>\n{{#each rows}}<tr><td>{{this.no}}</td><td>{{this.name}}</td><td>{{this.department}}</td><td>{{this.version}}</td><td>{{this.completedOn}}</td><td>{{this.minutes}}</td><td>{{this.score}}</td><td>{{this.certificateNo}}</td></tr>{{/each}}\n</table>\n<div class=\"date\">{{issue.issuedOn}}</div>\n${ISSUER}\n</div></body></html>`\n\nconst dayOf = (at: Date | string | null | undefined, timezone?: string | null) =>\n at ? new Intl.DateTimeFormat('en-CA', { timeZone: timezone || 'UTC', year: 'numeric', month: '2-digit', day: '2-digit' }).format(new Date(at)) : ''\n\nasync function enrollmentFor(context: any, id: string): Promise<Enrollment | null> {\n return id ? await getRepository(Enrollment, context.state.tx).findOne({ where: { id, domain: { id: context.state.domain.id } } as any }) : null\n}\n\n/** A certificate is issued and read by the person it is of, once completed, and by a manager. */\nasync function mayHaveCertificate(context: any, id: string | null | undefined): Promise<boolean> {\n const enrollment = await enrollmentFor(context, id ?? '')\n if (!enrollment || enrollment.status !== EnrollmentStatus.COMPLETED) return false\n return enrollment.userId === context.state.user?.id || (await requestHolds(context, 'course', 'query'))\n}\n\nexport function registerLearningDocuments(): void {\n registerIssuedDocumentKind({\n kind: LEARNING_CERTIFICATE_KIND,\n prefix: '수료',\n refType: 'Enrollment',\n template: { content_template: CERTIFICATE_TEMPLATE, page_size: 'A4' },\n async dataOf(context, refId) {\n const enrollment = await enrollmentFor(context, refId)\n if (!enrollment || enrollment.status !== EnrollmentStatus.COMPLETED) throw new Refusal('learning-not-completed', {}, 'a certificate is issued for a completed enrolment')\n const tx = context.state.tx\n const course = await getRepository(Course, tx).findOneBy({ id: enrollment.courseId! })\n const version = await getRepository(CourseVersion, tx).findOne({ where: { id: enrollment.versionId! } as any, withDeleted: true })\n const learner = await getRepository(User, tx).findOneBy({ id: enrollment.userId! })\n return {\n subjectName: `${learner?.name ?? ''} · ${course?.name ?? ''}`,\n data: {\n learner: { name: learner?.name ?? '' },\n course: { name: course?.name ?? '', version: version?.number ?? '' },\n completion: {\n completedOn: dayOf(enrollment.completedAt, context.state.domain.timezone),\n minutes: Math.floor((enrollment.learnedSeconds ?? 0) / 60),\n score: enrollment.score ?? null,\n certificateNo: enrollment.certificateNo ?? ''\n }\n }\n }\n },\n async mayIssue(context, refId) {\n return await mayHaveCertificate(context, refId)\n },\n async mayRead(context, row) {\n return await mayHaveCertificate(context, row.refId)\n }\n })\n\n registerIssuedDocumentKind({\n kind: LEARNING_REGISTER_KIND,\n prefix: '이수대장',\n refType: 'Course',\n asOf: true,\n template: { content_template: REGISTER_TEMPLATE, page_size: 'A4' },\n async dataOf(context, refId, input) {\n const course = await getRepository(Course, context.state.tx).findOne({ where: { id: refId, domain: { id: context.state.domain.id } } as any })\n if (!course) throw new Refusal('learning-not-found', {}, 'the record was not found. reload and try again.')\n const timezone = context.state.domain.timezone\n const rows = (await completionRegister(course.id, context)).filter(row => !input.asOf || dayOf(row.enrollment.completedAt, timezone) <= input.asOf)\n return {\n subjectName: course.name,\n data: {\n course: { name: course.name },\n count: rows.length,\n rows: rows.map((row, index) => ({\n no: index + 1,\n name: row.user?.name ?? '',\n department: row.departmentName ?? '',\n version: row.versionNumber ?? '',\n completedOn: dayOf(row.enrollment.completedAt, timezone),\n minutes: Math.floor((row.enrollment.learnedSeconds ?? 0) / 60),\n score: row.enrollment.score ?? '',\n certificateNo: row.enrollment.certificateNo ?? ''\n }))\n }\n }\n },\n async mayIssue(context) {\n return await requestHolds(context, 'course', 'query')\n },\n async mayRead(context) {\n return await requestHolds(context, 'course', 'query')\n }\n })\n}\n"]}