@hed-hog/lms 0.0.319 → 0.0.320

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 (128) hide show
  1. package/dist/class-group/class-group.controller.d.ts +65 -2
  2. package/dist/class-group/class-group.controller.d.ts.map +1 -1
  3. package/dist/class-group/class-group.controller.js +35 -0
  4. package/dist/class-group/class-group.controller.js.map +1 -1
  5. package/dist/class-group/class-group.service.d.ts +67 -2
  6. package/dist/class-group/class-group.service.d.ts.map +1 -1
  7. package/dist/class-group/class-group.service.js +164 -13
  8. package/dist/class-group/class-group.service.js.map +1 -1
  9. package/dist/class-group/dto/create-class-group.dto.d.ts.map +1 -1
  10. package/dist/class-group/dto/create-class-group.dto.js +2 -1
  11. package/dist/class-group/dto/create-class-group.dto.js.map +1 -1
  12. package/dist/class-group/dto/material.dto.d.ts +18 -0
  13. package/dist/class-group/dto/material.dto.d.ts.map +1 -0
  14. package/dist/class-group/dto/material.dto.js +86 -0
  15. package/dist/class-group/dto/material.dto.js.map +1 -0
  16. package/dist/course/course.service.d.ts +2 -0
  17. package/dist/course/course.service.d.ts.map +1 -1
  18. package/dist/course/course.service.js +27 -2
  19. package/dist/course/course.service.js.map +1 -1
  20. package/dist/course/dto/create-course.dto.d.ts +2 -2
  21. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  22. package/dist/course/dto/create-course.dto.js.map +1 -1
  23. package/dist/enterprise/enterprise.controller.d.ts +7 -1
  24. package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
  25. package/dist/enterprise/enterprise.controller.js +72 -2
  26. package/dist/enterprise/enterprise.controller.js.map +1 -1
  27. package/dist/enterprise/enterprise.module.d.ts.map +1 -1
  28. package/dist/enterprise/enterprise.module.js +2 -1
  29. package/dist/enterprise/enterprise.module.js.map +1 -1
  30. package/dist/enterprise/enterprise.service.d.ts +3 -0
  31. package/dist/enterprise/enterprise.service.d.ts.map +1 -1
  32. package/dist/enterprise/enterprise.service.js +84 -1
  33. package/dist/enterprise/enterprise.service.js.map +1 -1
  34. package/dist/enterprise/training/enterprise-training.module.d.ts +3 -0
  35. package/dist/enterprise/training/enterprise-training.module.d.ts.map +1 -0
  36. package/dist/enterprise/training/enterprise-training.module.js +40 -0
  37. package/dist/enterprise/training/enterprise-training.module.js.map +1 -0
  38. package/dist/enterprise/training/training-admin.controller.d.ts +525 -0
  39. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -0
  40. package/dist/enterprise/training/training-admin.controller.js +385 -0
  41. package/dist/enterprise/training/training-admin.controller.js.map +1 -0
  42. package/dist/enterprise/training/training-admin.service.d.ts +582 -0
  43. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -0
  44. package/dist/enterprise/training/training-admin.service.js +2283 -0
  45. package/dist/enterprise/training/training-admin.service.js.map +1 -0
  46. package/dist/enterprise/training/training-instructor.controller.d.ts +260 -0
  47. package/dist/enterprise/training/training-instructor.controller.d.ts.map +1 -0
  48. package/dist/enterprise/training/training-instructor.controller.js +199 -0
  49. package/dist/enterprise/training/training-instructor.controller.js.map +1 -0
  50. package/dist/enterprise/training/training-instructor.service.d.ts +280 -0
  51. package/dist/enterprise/training/training-instructor.service.d.ts.map +1 -0
  52. package/dist/enterprise/training/training-instructor.service.js +1218 -0
  53. package/dist/enterprise/training/training-instructor.service.js.map +1 -0
  54. package/dist/enterprise/training/training-student.controller.d.ts +168 -0
  55. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -0
  56. package/dist/enterprise/training/training-student.controller.js +104 -0
  57. package/dist/enterprise/training/training-student.controller.js.map +1 -0
  58. package/dist/enterprise/training/training-student.service.d.ts +185 -0
  59. package/dist/enterprise/training/training-student.service.d.ts.map +1 -0
  60. package/dist/enterprise/training/training-student.service.js +674 -0
  61. package/dist/enterprise/training/training-student.service.js.map +1 -0
  62. package/dist/enterprise/training/training-viewer.controller.d.ts +298 -0
  63. package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -0
  64. package/dist/enterprise/training/training-viewer.controller.js +178 -0
  65. package/dist/enterprise/training/training-viewer.controller.js.map +1 -0
  66. package/dist/evaluation/dto/create-evaluation-topic.dto.d.ts +18 -0
  67. package/dist/evaluation/dto/create-evaluation-topic.dto.d.ts.map +1 -0
  68. package/dist/evaluation/dto/create-evaluation-topic.dto.js +59 -0
  69. package/dist/evaluation/dto/create-evaluation-topic.dto.js.map +1 -0
  70. package/dist/evaluation/dto/update-evaluation-topic.dto.d.ts +6 -0
  71. package/dist/evaluation/dto/update-evaluation-topic.dto.d.ts.map +1 -0
  72. package/dist/evaluation/dto/update-evaluation-topic.dto.js +9 -0
  73. package/dist/evaluation/dto/update-evaluation-topic.dto.js.map +1 -0
  74. package/dist/evaluation/evaluation.controller.d.ts +66 -0
  75. package/dist/evaluation/evaluation.controller.d.ts.map +1 -1
  76. package/dist/evaluation/evaluation.controller.js +73 -0
  77. package/dist/evaluation/evaluation.controller.js.map +1 -1
  78. package/dist/evaluation/evaluation.service.d.ts +71 -0
  79. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  80. package/dist/evaluation/evaluation.service.js +121 -0
  81. package/dist/evaluation/evaluation.service.js.map +1 -1
  82. package/dist/instructor/instructor.service.js +6 -6
  83. package/dist/instructor/instructor.service.js.map +1 -1
  84. package/dist/lms.module.d.ts.map +1 -1
  85. package/dist/lms.module.js +3 -0
  86. package/dist/lms.module.js.map +1 -1
  87. package/hedhog/data/menu.yaml +19 -2
  88. package/hedhog/data/route.yaml +730 -0
  89. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +74 -8
  90. package/hedhog/frontend/app/_components/course-avatar.tsx.ejs +27 -47
  91. package/hedhog/frontend/app/classes/[id]/_components/event-summary-popover.tsx.ejs +15 -15
  92. package/hedhog/frontend/app/classes/[id]/_components/quick-create-session-popover.tsx.ejs +5 -5
  93. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2141 -308
  94. package/hedhog/frontend/app/classes/page.tsx.ejs +8 -7
  95. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +21 -8
  96. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +10 -6
  97. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-form-sheet.tsx.ejs +201 -0
  98. package/hedhog/frontend/app/evaluations/_components/evaluation-topic-types.ts.ejs +49 -0
  99. package/hedhog/frontend/app/evaluations/page.tsx.ejs +621 -1250
  100. package/hedhog/frontend/app/instructors/page.tsx.ejs +22 -20
  101. package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +1278 -0
  102. package/hedhog/frontend/messages/en.json +98 -7
  103. package/hedhog/frontend/messages/pt.json +98 -7
  104. package/hedhog/table/course_class_group_material.yaml +45 -0
  105. package/package.json +8 -8
  106. package/src/class-group/class-group.controller.ts +30 -0
  107. package/src/class-group/class-group.service.ts +176 -5
  108. package/src/class-group/dto/create-class-group.dto.ts +8 -8
  109. package/src/class-group/dto/material.dto.ts +69 -0
  110. package/src/course/course.service.ts +54 -21
  111. package/src/course/dto/create-course.dto.ts +8 -8
  112. package/src/enterprise/enterprise.controller.ts +62 -1
  113. package/src/enterprise/enterprise.module.ts +2 -1
  114. package/src/enterprise/enterprise.service.ts +84 -1
  115. package/src/enterprise/training/enterprise-training.module.ts +27 -0
  116. package/src/enterprise/training/training-admin.controller.ts +278 -0
  117. package/src/enterprise/training/training-admin.service.ts +2523 -0
  118. package/src/enterprise/training/training-instructor.controller.ts +141 -0
  119. package/src/enterprise/training/training-instructor.service.ts +1303 -0
  120. package/src/enterprise/training/training-student.controller.ts +65 -0
  121. package/src/enterprise/training/training-student.service.ts +762 -0
  122. package/src/enterprise/training/training-viewer.controller.ts +115 -0
  123. package/src/evaluation/dto/create-evaluation-topic.dto.ts +48 -0
  124. package/src/evaluation/dto/update-evaluation-topic.dto.ts +6 -0
  125. package/src/evaluation/evaluation.controller.ts +63 -1
  126. package/src/evaluation/evaluation.service.ts +150 -1
  127. package/src/instructor/instructor.service.ts +4 -4
  128. package/src/lms.module.ts +3 -0
@@ -4,11 +4,10 @@ import {
4
4
  IsInt,
5
5
  IsISO8601,
6
6
  IsNotEmpty,
7
- IsOptional,
8
- IsString,
9
- IsUrl,
10
- Matches,
11
- MaxLength,
7
+ IsOptional,
8
+ IsString,
9
+ Matches,
10
+ MaxLength,
12
11
  Min,
13
12
  ValidateNested,
14
13
  } from 'class-validator';
@@ -128,9 +127,10 @@ export class CreateClassGroupDto {
128
127
  @IsOptional()
129
128
  location?: string;
130
129
 
131
- @IsUrl({ require_tld: false })
132
- @IsOptional()
133
- virtualRoomUrl?: string;
130
+ @IsString()
131
+ @MaxLength(500)
132
+ @IsOptional()
133
+ virtualRoomUrl?: string;
134
134
 
135
135
  @ValidateNested()
136
136
  @Type(() => ClassGroupSessionTemplateDto)
@@ -0,0 +1,69 @@
1
+ import {
2
+ IsEnum,
3
+ IsInt,
4
+ IsNotEmpty,
5
+ IsOptional,
6
+ IsString,
7
+ MaxLength,
8
+ Min
9
+ } from 'class-validator';
10
+
11
+ export type MaterialType = 'file' | 'link';
12
+
13
+ export class CreateMaterialDto {
14
+ @IsString()
15
+ @IsNotEmpty()
16
+ @MaxLength(255)
17
+ title: string;
18
+
19
+ @IsString()
20
+ @IsOptional()
21
+ description?: string;
22
+
23
+ @IsEnum(['file', 'link'])
24
+ materialType: MaterialType;
25
+
26
+ @IsInt()
27
+ @Min(1)
28
+ @IsOptional()
29
+ fileId?: number;
30
+
31
+ @IsString()
32
+ @MaxLength(500)
33
+ @IsOptional()
34
+ url?: string;
35
+
36
+ @IsInt()
37
+ @IsOptional()
38
+ sessionId?: number;
39
+
40
+ @IsInt()
41
+ @IsOptional()
42
+ sortOrder?: number;
43
+ }
44
+
45
+ export class UpdateMaterialDto {
46
+ @IsString()
47
+ @IsNotEmpty()
48
+ @MaxLength(255)
49
+ @IsOptional()
50
+ title?: string;
51
+
52
+ @IsString()
53
+ @IsOptional()
54
+ description?: string;
55
+
56
+ @IsInt()
57
+ @Min(1)
58
+ @IsOptional()
59
+ fileId?: number;
60
+
61
+ @IsString()
62
+ @MaxLength(500)
63
+ @IsOptional()
64
+ url?: string;
65
+
66
+ @IsInt()
67
+ @IsOptional()
68
+ sortOrder?: number;
69
+ }
@@ -822,27 +822,60 @@ export class CourseService {
822
822
  }
823
823
  }
824
824
 
825
- private async syncCourseImages(
826
- courseId: number,
827
- data: {
828
- logoFileId?: number;
829
- bannerFileId?: number;
830
- },
831
- ) {
832
- if (data.logoFileId !== undefined) {
833
- await this.upsertCourseImage(courseId, data.logoFileId, 'course-logo');
834
- }
835
-
836
- if (data.bannerFileId !== undefined) {
837
- await this.upsertCourseImage(
838
- courseId,
839
- data.bannerFileId,
840
- 'course-banner',
841
- );
842
- }
843
- }
844
-
845
- private async upsertCourseImage(
825
+ private async syncCourseImages(
826
+ courseId: number,
827
+ data: {
828
+ logoFileId?: number | null;
829
+ bannerFileId?: number | null;
830
+ },
831
+ ) {
832
+ if (data.logoFileId !== undefined) {
833
+ await this.syncCourseImage(courseId, data.logoFileId, 'course-logo');
834
+ }
835
+
836
+ if (data.bannerFileId !== undefined) {
837
+ await this.syncCourseImage(courseId, data.bannerFileId, 'course-banner');
838
+ }
839
+ }
840
+
841
+ private async syncCourseImage(
842
+ courseId: number,
843
+ fileId: number | null,
844
+ imageTypeSlug: CourseImageTypeSlug,
845
+ ) {
846
+ if (fileId === null) {
847
+ await this.removeCourseImage(courseId, imageTypeSlug);
848
+ return;
849
+ }
850
+
851
+ await this.upsertCourseImage(courseId, fileId, imageTypeSlug);
852
+ }
853
+
854
+ private async removeCourseImage(
855
+ courseId: number,
856
+ imageTypeSlug: CourseImageTypeSlug,
857
+ ) {
858
+ const imageType = await this.prisma.image_type.findFirst({
859
+ where: {
860
+ slug: imageTypeSlug,
861
+ applies_to_course: true,
862
+ },
863
+ select: { id: true },
864
+ });
865
+
866
+ if (!imageType) {
867
+ return;
868
+ }
869
+
870
+ await this.prisma.course_image.deleteMany({
871
+ where: {
872
+ course_id: courseId,
873
+ image_type_id: imageType.id,
874
+ },
875
+ });
876
+ }
877
+
878
+ private async upsertCourseImage(
846
879
  courseId: number,
847
880
  fileId: number,
848
881
  imageTypeSlug: CourseImageTypeSlug,
@@ -104,11 +104,11 @@ export class CreateCourseDto {
104
104
  @IsOptional()
105
105
  instructorIds?: number[];
106
106
 
107
- @IsInt()
108
- @IsOptional()
109
- logoFileId?: number;
110
-
111
- @IsInt()
112
- @IsOptional()
113
- bannerFileId?: number;
114
- }
107
+ @IsInt()
108
+ @IsOptional()
109
+ logoFileId?: number | null;
110
+
111
+ @IsInt()
112
+ @IsOptional()
113
+ bannerFileId?: number | null;
114
+ }
@@ -1,15 +1,21 @@
1
1
  import { Public, Role, UserOptional } from '@hed-hog/api';
2
+ import { Locale } from '@hed-hog/api-locale';
3
+ import { FileService } from '@hed-hog/core';
2
4
  import {
3
5
  Body,
4
6
  Controller,
5
7
  Delete,
8
+ forwardRef,
6
9
  Get,
10
+ Inject,
7
11
  Param,
8
12
  ParseIntPipe,
9
13
  Patch,
10
14
  Post,
11
15
  Query,
16
+ Res,
12
17
  } from '@nestjs/common';
18
+ import { Response } from 'express';
13
19
  import { AddEnterpriseClassGroupDto } from './dto/add-enterprise-class-group.dto';
14
20
  import { AddEnterpriseCourseDto } from './dto/add-enterprise-course.dto';
15
21
  import { AddEnterpriseStudentDto } from './dto/add-enterprise-student.dto';
@@ -23,7 +29,62 @@ import { EnterpriseService } from './enterprise.service';
23
29
  @Role()
24
30
  @Controller('lms/enterprise')
25
31
  export class EnterpriseController {
26
- constructor(private readonly enterpriseService: EnterpriseService) {}
32
+ constructor(
33
+ private readonly enterpriseService: EnterpriseService,
34
+ @Inject(forwardRef(() => FileService))
35
+ private readonly fileService: FileService,
36
+ ) {}
37
+
38
+ @Public()
39
+ @Get('course/:courseId/logo')
40
+ async courseLogo(
41
+ @Param('courseId', ParseIntPipe) courseId: number,
42
+ @Res() res: Response,
43
+ @Locale() locale: string,
44
+ ) {
45
+ const fileId = await this.enterpriseService.getCourseLogoFileId(courseId);
46
+ const { stream, filename, mimetype, size } = await this.fileService.openById(locale, fileId);
47
+ res.set({
48
+ 'Content-Type': mimetype || 'image/octet-stream',
49
+ 'Content-Disposition': `inline; filename="${filename}"`,
50
+ });
51
+ if (typeof size === 'number') res.set('Content-Length', `${size}`);
52
+ stream.pipe(res);
53
+ }
54
+
55
+ @Public()
56
+ @Get('class/:classId/logo')
57
+ async classLogo(
58
+ @Param('classId', ParseIntPipe) classId: number,
59
+ @Res() res: Response,
60
+ @Locale() locale: string,
61
+ ) {
62
+ const fileId = await this.enterpriseService.getClassLogoFileId(classId);
63
+ const { stream, filename, mimetype, size } = await this.fileService.openById(locale, fileId);
64
+ res.set({
65
+ 'Content-Type': mimetype || 'image/octet-stream',
66
+ 'Content-Disposition': `inline; filename="${filename}"`,
67
+ });
68
+ if (typeof size === 'number') res.set('Content-Length', `${size}`);
69
+ stream.pipe(res);
70
+ }
71
+
72
+ @Public()
73
+ @Get('class/:classId/banner')
74
+ async classBanner(
75
+ @Param('classId', ParseIntPipe) classId: number,
76
+ @Res() res: Response,
77
+ @Locale() locale: string,
78
+ ) {
79
+ const fileId = await this.enterpriseService.getClassBannerFileId(classId);
80
+ const { stream, filename, mimetype, size } = await this.fileService.openById(locale, fileId);
81
+ res.set({
82
+ 'Content-Type': mimetype || 'image/octet-stream',
83
+ 'Content-Disposition': `inline; filename="${filename}"`,
84
+ });
85
+ if (typeof size === 'number') res.set('Content-Length', `${size}`);
86
+ stream.pipe(res);
87
+ }
27
88
 
28
89
  @Get()
29
90
  list(
@@ -1,10 +1,11 @@
1
+ import { FileModule } from '@hed-hog/core';
1
2
  import { PrismaModule } from '@hed-hog/api-prisma';
2
3
  import { forwardRef, Module } from '@nestjs/common';
3
4
  import { EnterpriseController } from './enterprise.controller';
4
5
  import { EnterpriseService } from './enterprise.service';
5
6
 
6
7
  @Module({
7
- imports: [forwardRef(() => PrismaModule)],
8
+ imports: [forwardRef(() => PrismaModule), forwardRef(() => FileModule)],
8
9
  controllers: [EnterpriseController],
9
10
  providers: [EnterpriseService],
10
11
  exports: [forwardRef(() => EnterpriseService)],
@@ -531,9 +531,33 @@ export class EnterpriseService {
531
531
  `Class group #${dto.course_class_group_id} is already linked to this enterprise`,
532
532
  );
533
533
 
534
- return this.prisma.enterprise_class_group.create({
534
+ const link = await this.prisma.enterprise_class_group.create({
535
535
  data: { enterprise_id: enterpriseId, ...(dto as any) },
536
536
  });
537
+
538
+ // Backfill existing enrolled students into enterprise_student
539
+ const enrolledPersons = await this.prisma.course_enrollment.findMany({
540
+ where: {
541
+ course_class_group_id: dto.course_class_group_id,
542
+ status: { not: 'cancelled' },
543
+ },
544
+ select: { person_id: true },
545
+ distinct: ['person_id'],
546
+ });
547
+
548
+ for (const { person_id } of enrolledPersons) {
549
+ const exists = await this.prisma.enterprise_student.findFirst({
550
+ where: { enterprise_id: enterpriseId, person_id },
551
+ select: { id: true },
552
+ });
553
+ if (!exists) {
554
+ await this.prisma.enterprise_student.create({
555
+ data: { enterprise_id: enterpriseId, person_id, status: 'active' },
556
+ });
557
+ }
558
+ }
559
+
560
+ return link;
537
561
  }
538
562
 
539
563
  async removeClassGroup(enterpriseId: number, classGroupId: number) {
@@ -691,6 +715,65 @@ export class EnterpriseService {
691
715
 
692
716
  // ─── Training Auth Profiles ──────────────────────────────────────────────────
693
717
 
718
+ async getCourseLogoFileId(courseId: number): Promise<number> {
719
+ const row = await this.prisma.course.findFirst({
720
+ where: { id: courseId },
721
+ select: {
722
+ course_image: {
723
+ where: { image_type: { slug: 'course-logo' } },
724
+ orderBy: { is_primary: 'desc' as const },
725
+ take: 1,
726
+ select: { file: { select: { id: true } } },
727
+ },
728
+ },
729
+ });
730
+ const fileId = row?.course_image?.[0]?.file?.id;
731
+ if (!fileId) throw new NotFoundException(`Logo not found for course #${courseId}`);
732
+ return fileId;
733
+ }
734
+
735
+ async getClassLogoFileId(classId: number): Promise<number> {
736
+ const row = await this.prisma.course_class_group.findFirst({
737
+ where: { id: classId },
738
+ select: {
739
+ course: {
740
+ select: {
741
+ course_image: {
742
+ where: { image_type: { slug: 'course-logo' } },
743
+ orderBy: { is_primary: 'desc' as const },
744
+ take: 1,
745
+ select: { file: { select: { id: true } } },
746
+ },
747
+ },
748
+ },
749
+ },
750
+ });
751
+ const fileId = row?.course?.course_image?.[0]?.file?.id;
752
+ if (!fileId) throw new NotFoundException(`Logo not found for class #${classId}`);
753
+ return fileId;
754
+ }
755
+
756
+ async getClassBannerFileId(classId: number): Promise<number> {
757
+ const row = await this.prisma.course_class_group.findFirst({
758
+ where: { id: classId },
759
+ select: {
760
+ course: {
761
+ select: {
762
+ course_image: {
763
+ where: { image_type: { slug: 'course-banner' } },
764
+ orderBy: { is_primary: 'desc' as const },
765
+ take: 1,
766
+ select: { file: { select: { id: true } } },
767
+ },
768
+ },
769
+ },
770
+ },
771
+ });
772
+ const fileId = row?.course?.course_image?.[0]?.file?.id;
773
+ if (!fileId) throw new NotFoundException(`Banner not found for class #${classId}`);
774
+ return fileId;
775
+ }
776
+
694
777
  async getProfilesForUser(userId: number): Promise<EnterpriseProfile[]> {
695
778
  const ROLE_TO_SLUG: Record<string, string> = {
696
779
  hr_manager: 'lms-enterprise-admin',
@@ -0,0 +1,27 @@
1
+ import { PrismaModule } from '@hed-hog/api-prisma';
2
+ import { FileModule } from '@hed-hog/core';
3
+ import { forwardRef, Module } from '@nestjs/common';
4
+ import { TrainingAdminController } from './training-admin.controller';
5
+ import { TrainingAdminService } from './training-admin.service';
6
+ import { TrainingInstructorController } from './training-instructor.controller';
7
+ import { TrainingInstructorService } from './training-instructor.service';
8
+ import { TrainingStudentController } from './training-student.controller';
9
+ import { TrainingStudentService } from './training-student.service';
10
+ import { TrainingViewerController } from './training-viewer.controller';
11
+
12
+ @Module({
13
+ imports: [forwardRef(() => PrismaModule), forwardRef(() => FileModule)],
14
+ controllers: [
15
+ TrainingAdminController,
16
+ TrainingViewerController,
17
+ TrainingInstructorController,
18
+ TrainingStudentController,
19
+ ],
20
+ providers: [TrainingAdminService, TrainingInstructorService, TrainingStudentService],
21
+ exports: [
22
+ forwardRef(() => TrainingAdminService),
23
+ forwardRef(() => TrainingInstructorService),
24
+ forwardRef(() => TrainingStudentService),
25
+ ],
26
+ })
27
+ export class EnterpriseTrainingModule {}