@hed-hog/lms 0.0.331 → 0.0.347
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.
- package/dist/class-group/class-group.controller.d.ts +8 -8
- package/dist/class-group/class-group.service.d.ts +8 -8
- package/dist/course/course.controller.d.ts +6 -1
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.controller.js +19 -2
- package/dist/course/course.controller.js.map +1 -1
- package/dist/course/course.service.d.ts +6 -0
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +63 -28
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course.dto.d.ts +1 -0
- package/dist/course/dto/create-course.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course.dto.js +5 -0
- package/dist/course/dto/create-course.dto.js.map +1 -1
- package/dist/enterprise/enterprise.controller.d.ts +84 -12
- package/dist/enterprise/enterprise.controller.d.ts.map +1 -1
- package/dist/enterprise/enterprise.controller.js +10 -0
- package/dist/enterprise/enterprise.controller.js.map +1 -1
- package/dist/enterprise/enterprise.service.d.ts +90 -12
- package/dist/enterprise/enterprise.service.d.ts.map +1 -1
- package/dist/enterprise/enterprise.service.js +413 -40
- package/dist/enterprise/enterprise.service.js.map +1 -1
- package/dist/enterprise/training/training-admin.controller.d.ts +9 -6
- package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.controller.js +10 -6
- package/dist/enterprise/training/training-admin.controller.js.map +1 -1
- package/dist/enterprise/training/training-admin.service.d.ts +11 -5
- package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-admin.service.js +108 -52
- package/dist/enterprise/training/training-admin.service.js.map +1 -1
- package/dist/enterprise/training/training-viewer.controller.d.ts +3 -0
- package/dist/enterprise/training/training-viewer.controller.d.ts.map +1 -1
- package/dist/evaluation/evaluation.controller.d.ts +2 -2
- package/dist/evaluation/evaluation.service.d.ts +2 -2
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/create-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/create-instructor-skill.dto.js +0 -21
- package/dist/instructor/dto/create-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts +0 -4
- package/dist/instructor/dto/update-instructor-skill.dto.d.ts.map +1 -1
- package/dist/instructor/dto/update-instructor-skill.dto.js +0 -22
- package/dist/instructor/dto/update-instructor-skill.dto.js.map +1 -1
- package/dist/instructor/instructor-skill.controller.d.ts +4 -4
- package/dist/instructor/instructor-skill.service.d.ts +4 -7
- package/dist/instructor/instructor-skill.service.d.ts.map +1 -1
- package/dist/instructor/instructor-skill.service.js +2 -89
- package/dist/instructor/instructor-skill.service.js.map +1 -1
- package/dist/instructor/instructor.controller.d.ts +21 -0
- package/dist/instructor/instructor.controller.d.ts.map +1 -1
- package/dist/instructor/instructor.controller.js +19 -0
- package/dist/instructor/instructor.controller.js.map +1 -1
- package/dist/instructor/instructor.service.d.ts +27 -0
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +79 -25
- package/dist/instructor/instructor.service.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js.map +1 -1
- package/dist/training/dto/create-training.dto.d.ts +1 -0
- package/dist/training/dto/create-training.dto.d.ts.map +1 -1
- package/dist/training/dto/create-training.dto.js +5 -0
- package/dist/training/dto/create-training.dto.js.map +1 -1
- package/dist/training/training.controller.d.ts +4 -0
- package/dist/training/training.controller.d.ts.map +1 -1
- package/dist/training/training.service.d.ts +8 -0
- package/dist/training/training.service.d.ts.map +1 -1
- package/dist/training/training.service.js +71 -6
- package/dist/training/training.service.js.map +1 -1
- package/hedhog/data/route.yaml +23 -1
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +80 -33
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +3 -3
- package/hedhog/frontend/app/_components/create-lms-instructor-sheet.tsx.ejs +591 -0
- package/hedhog/frontend/app/certificates/issued/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +39 -7
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +1 -3
- package/hedhog/frontend/app/classes/page.tsx.ejs +34 -7
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +3 -33
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +9 -9
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +109 -0
- package/hedhog/frontend/app/courses/[id]/_components/CourseMultiEntityPicker.tsx.ejs +40 -13
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +76 -81
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +60 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-scheduled-classes-tab.tsx.ejs +406 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +243 -34
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +185 -0
- package/hedhog/frontend/app/courses/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-activity-timeline.tsx.ejs +87 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-admin-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-administrators-tab.tsx.ejs +31 -5
- package/hedhog/frontend/app/enterprise/_components/enterprise-classes-tab.tsx.ejs +79 -20
- package/hedhog/frontend/app/enterprise/_components/enterprise-company-identity-card.tsx.ejs +11 -2
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +201 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-courses-tab.tsx.ejs +55 -24
- package/hedhog/frontend/app/enterprise/_components/enterprise-detail-sheet.tsx.ejs +430 -296
- package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +277 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-overview-analytics.tsx.ejs +205 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-person-edit-sheet.tsx.ejs +97 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-sheet.tsx.ejs +82 -57
- package/hedhog/frontend/app/enterprise/_components/enterprise-student-create-sheet.tsx.ejs +4 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-students-tab.tsx.ejs +60 -22
- package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +54 -0
- package/hedhog/frontend/app/enterprise/_components/enterprise-user-create-sheet.tsx.ejs +211 -0
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -7
- package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +31 -19
- package/hedhog/frontend/app/exams/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +51 -104
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +625 -366
- package/hedhog/frontend/app/instructors/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/paths/page.tsx.ejs +76 -8
- package/hedhog/frontend/app/reports/evaluations/page.tsx.ejs +6 -1
- package/hedhog/frontend/app/training/page.tsx.ejs +78 -9
- package/hedhog/frontend/messages/en.json +101 -10
- package/hedhog/frontend/messages/pt.json +115 -11
- package/hedhog/table/enterprise_student_license_event.yaml +30 -0
- package/hedhog/table/instructor_skill.yaml +0 -11
- package/hedhog/table/learning_path.yaml +4 -0
- package/package.json +6 -6
- package/src/course/course.controller.ts +18 -0
- package/src/course/course.service.ts +85 -26
- package/src/course/dto/create-course.dto.ts +4 -0
- package/src/enterprise/enterprise.controller.ts +5 -0
- package/src/enterprise/enterprise.service.ts +507 -29
- package/src/enterprise/training/training-admin.controller.ts +4 -0
- package/src/enterprise/training/training-admin.service.ts +115 -51
- package/src/instructor/dto/create-instructor-skill.dto.ts +0 -17
- package/src/instructor/dto/update-instructor-skill.dto.ts +0 -18
- package/src/instructor/instructor-skill.service.ts +2 -97
- package/src/instructor/instructor.controller.ts +16 -0
- package/src/instructor/instructor.service.ts +87 -10
- package/src/lms.module.ts +1 -0
- package/src/training/dto/create-training.dto.ts +4 -0
- package/src/training/training.service.ts +104 -5
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PrismaService } from '@hed-hog/api-prisma';
|
|
1
|
+
import { Prisma, PrismaService } from '@hed-hog/api-prisma';
|
|
2
2
|
import {
|
|
3
3
|
BadRequestException,
|
|
4
4
|
ConflictException,
|
|
@@ -11,6 +11,30 @@ import {
|
|
|
11
11
|
export class TrainingAdminService {
|
|
12
12
|
constructor(private readonly prisma: PrismaService) {}
|
|
13
13
|
|
|
14
|
+
private async recordLicenseEvent(params: {
|
|
15
|
+
enterpriseId: number;
|
|
16
|
+
personId: number;
|
|
17
|
+
eventType: 'assigned' | 'revoked' | 'status_changed';
|
|
18
|
+
previousStatus?: string | null;
|
|
19
|
+
nextStatus?: string | null;
|
|
20
|
+
}) {
|
|
21
|
+
const eventModel = (this.prisma as any).enterprise_student_license_event;
|
|
22
|
+
if (!eventModel) return;
|
|
23
|
+
try {
|
|
24
|
+
await eventModel.create({
|
|
25
|
+
data: {
|
|
26
|
+
enterprise_id: params.enterpriseId,
|
|
27
|
+
person_id: params.personId,
|
|
28
|
+
event_type: params.eventType,
|
|
29
|
+
previous_status: params.previousStatus ?? null,
|
|
30
|
+
next_status: params.nextStatus ?? null,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
} catch {
|
|
34
|
+
// Keep license operations working until the YAML table is applied.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
14
38
|
async getDashboard(userId: number, requestedEnterpriseId?: number) {
|
|
15
39
|
if (requestedEnterpriseId !== undefined) {
|
|
16
40
|
const allowed = await this.prisma.enterprise_user.findFirst({
|
|
@@ -311,6 +335,8 @@ export class TrainingAdminService {
|
|
|
311
335
|
userId: number,
|
|
312
336
|
options: {
|
|
313
337
|
enterpriseId?: number;
|
|
338
|
+
page?: number;
|
|
339
|
+
pageSize?: number;
|
|
314
340
|
search?: string;
|
|
315
341
|
status?: string;
|
|
316
342
|
deliveryMode?: string;
|
|
@@ -340,62 +366,71 @@ export class TrainingAdminService {
|
|
|
340
366
|
}
|
|
341
367
|
|
|
342
368
|
const enterpriseId = enterpriseUser.enterprise_id;
|
|
343
|
-
const { search, status, deliveryMode, instructorId } = options;
|
|
369
|
+
const { page = 1, pageSize = 10, search, status, deliveryMode, instructorId } = options;
|
|
344
370
|
const now = new Date();
|
|
345
371
|
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
],
|
|
361
|
-
},
|
|
362
|
-
]
|
|
363
|
-
: []),
|
|
364
|
-
],
|
|
365
|
-
},
|
|
366
|
-
orderBy: { start_date: 'desc' },
|
|
367
|
-
include: {
|
|
368
|
-
course: {
|
|
369
|
-
select: {
|
|
370
|
-
id: true,
|
|
371
|
-
title: true,
|
|
372
|
-
course_image: {
|
|
373
|
-
where: { image_type: { slug: { in: ['course-logo', 'course-banner'] } } },
|
|
374
|
-
orderBy: { is_primary: 'desc' as const },
|
|
375
|
-
select: {
|
|
376
|
-
file: { select: { location: true } },
|
|
377
|
-
image_type: { select: { slug: true } },
|
|
372
|
+
const where = {
|
|
373
|
+
AND: [
|
|
374
|
+
{ enterprise_class_group: { some: { enterprise_id: enterpriseId } } },
|
|
375
|
+
...(status ? [{ status: status as any }] : []),
|
|
376
|
+
...(deliveryMode ? [{ delivery_mode: deliveryMode as any }] : []),
|
|
377
|
+
...(instructorId !== undefined ? [{ instructor_id: instructorId }] : []),
|
|
378
|
+
...(search
|
|
379
|
+
? [
|
|
380
|
+
{
|
|
381
|
+
OR: [
|
|
382
|
+
{ title: { contains: search, mode: 'insensitive' as const } },
|
|
383
|
+
{ code: { contains: search, mode: 'insensitive' as const } },
|
|
384
|
+
{ course: { title: { contains: search, mode: 'insensitive' as const } } },
|
|
385
|
+
],
|
|
378
386
|
},
|
|
387
|
+
]
|
|
388
|
+
: []),
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const include = Prisma.validator<Prisma.course_class_groupInclude>()({
|
|
393
|
+
course: {
|
|
394
|
+
select: {
|
|
395
|
+
id: true,
|
|
396
|
+
title: true,
|
|
397
|
+
course_image: {
|
|
398
|
+
where: { image_type: { slug: { in: ['course-logo', 'course-banner'] } } },
|
|
399
|
+
orderBy: { is_primary: 'desc' as const },
|
|
400
|
+
select: {
|
|
401
|
+
file: { select: { location: true } },
|
|
402
|
+
image_type: { select: { slug: true } },
|
|
379
403
|
},
|
|
380
404
|
},
|
|
381
405
|
},
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
},
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
},
|
|
406
|
+
},
|
|
407
|
+
instructor: {
|
|
408
|
+
include: { person: { select: { name: true } } },
|
|
409
|
+
},
|
|
410
|
+
_count: {
|
|
411
|
+
select: {
|
|
412
|
+
course_enrollment: { where: { status: { not: 'cancelled' } } },
|
|
389
413
|
},
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
414
|
+
},
|
|
415
|
+
course_class_session: {
|
|
416
|
+
where: { session_date: { gte: now } },
|
|
417
|
+
orderBy: { session_date: 'asc' as const },
|
|
418
|
+
take: 1,
|
|
394
419
|
select: { session_date: true, start_time: true, location: true },
|
|
395
|
-
},
|
|
396
420
|
},
|
|
397
421
|
});
|
|
398
422
|
|
|
423
|
+
const [classGroups, total] = await this.prisma.$transaction([
|
|
424
|
+
this.prisma.course_class_group.findMany({
|
|
425
|
+
where,
|
|
426
|
+
orderBy: { start_date: 'desc' },
|
|
427
|
+
include,
|
|
428
|
+
skip: (page - 1) * pageSize,
|
|
429
|
+
take: pageSize,
|
|
430
|
+
}),
|
|
431
|
+
this.prisma.course_class_group.count({ where }),
|
|
432
|
+
]);
|
|
433
|
+
|
|
399
434
|
const cgIds = classGroups.map((cg) => cg.id);
|
|
400
435
|
|
|
401
436
|
const completedCounts =
|
|
@@ -453,7 +488,7 @@ export class TrainingAdminService {
|
|
|
453
488
|
};
|
|
454
489
|
});
|
|
455
490
|
|
|
456
|
-
return { data, total:
|
|
491
|
+
return { data, total, page, pageSize, lastPage: Math.ceil(total / pageSize) };
|
|
457
492
|
}
|
|
458
493
|
|
|
459
494
|
// ── Class-group detail endpoints ─────────────────────────────────────────────
|
|
@@ -830,17 +865,32 @@ export class TrainingAdminService {
|
|
|
830
865
|
if (existingStudent.status !== 'inactive') {
|
|
831
866
|
throw new ConflictException('This person already has an active license');
|
|
832
867
|
}
|
|
833
|
-
|
|
868
|
+
const updated = await this.prisma.enterprise_student.update({
|
|
834
869
|
where: { id: existingStudent.id },
|
|
835
870
|
data: { status: 'active' },
|
|
836
871
|
select: { id: true, person_id: true, status: true, created_at: true },
|
|
837
872
|
});
|
|
873
|
+
await this.recordLicenseEvent({
|
|
874
|
+
enterpriseId,
|
|
875
|
+
personId,
|
|
876
|
+
eventType: 'status_changed',
|
|
877
|
+
previousStatus: existingStudent.status,
|
|
878
|
+
nextStatus: 'active',
|
|
879
|
+
});
|
|
880
|
+
return updated;
|
|
838
881
|
}
|
|
839
882
|
|
|
840
|
-
|
|
883
|
+
const createdStudent = await this.prisma.enterprise_student.create({
|
|
841
884
|
data: { enterprise_id: enterpriseId, person_id: personId, status: 'pending' },
|
|
842
885
|
select: { id: true, person_id: true, status: true, created_at: true },
|
|
843
886
|
});
|
|
887
|
+
await this.recordLicenseEvent({
|
|
888
|
+
enterpriseId,
|
|
889
|
+
personId,
|
|
890
|
+
eventType: 'assigned',
|
|
891
|
+
nextStatus: 'pending',
|
|
892
|
+
});
|
|
893
|
+
return createdStudent;
|
|
844
894
|
}
|
|
845
895
|
|
|
846
896
|
async revokeLicense(userId: number, personId: number) {
|
|
@@ -852,15 +902,23 @@ export class TrainingAdminService {
|
|
|
852
902
|
|
|
853
903
|
const existing = await this.prisma.enterprise_student.findFirst({
|
|
854
904
|
where: { enterprise_id: enterpriseUser.enterprise_id, person_id: personId },
|
|
855
|
-
select: { id: true },
|
|
905
|
+
select: { id: true, status: true },
|
|
856
906
|
});
|
|
857
907
|
if (!existing) throw new NotFoundException('Student not found in your enterprise');
|
|
858
908
|
|
|
859
|
-
|
|
909
|
+
const updated = await this.prisma.enterprise_student.update({
|
|
860
910
|
where: { id: existing.id },
|
|
861
911
|
data: { status: 'inactive' },
|
|
862
912
|
select: { id: true },
|
|
863
913
|
});
|
|
914
|
+
await this.recordLicenseEvent({
|
|
915
|
+
enterpriseId: enterpriseUser.enterprise_id,
|
|
916
|
+
personId,
|
|
917
|
+
eventType: 'revoked',
|
|
918
|
+
previousStatus: existing.status,
|
|
919
|
+
nextStatus: 'inactive',
|
|
920
|
+
});
|
|
921
|
+
return updated;
|
|
864
922
|
}
|
|
865
923
|
|
|
866
924
|
// ── Enrollment methods ────────────────────────────────────────────────────────
|
|
@@ -1078,6 +1136,12 @@ export class TrainingAdminService {
|
|
|
1078
1136
|
},
|
|
1079
1137
|
select: { id: true },
|
|
1080
1138
|
});
|
|
1139
|
+
await this.recordLicenseEvent({
|
|
1140
|
+
enterpriseId,
|
|
1141
|
+
personId,
|
|
1142
|
+
eventType: 'assigned',
|
|
1143
|
+
nextStatus: 'active',
|
|
1144
|
+
});
|
|
1081
1145
|
}
|
|
1082
1146
|
|
|
1083
1147
|
const existingEnrollment = await this.prisma.course_enrollment.findFirst({
|
|
@@ -5,23 +5,6 @@ export class CreateInstructorSkillDto {
|
|
|
5
5
|
@MaxLength(255)
|
|
6
6
|
slug: string;
|
|
7
7
|
|
|
8
|
-
@IsString()
|
|
9
|
-
@MaxLength(255)
|
|
10
|
-
namePt: string;
|
|
11
|
-
|
|
12
|
-
@IsOptional()
|
|
13
|
-
@IsString()
|
|
14
|
-
@MaxLength(255)
|
|
15
|
-
nameEn?: string;
|
|
16
|
-
|
|
17
|
-
@IsOptional()
|
|
18
|
-
@IsString()
|
|
19
|
-
descriptionPt?: string;
|
|
20
|
-
|
|
21
|
-
@IsOptional()
|
|
22
|
-
@IsString()
|
|
23
|
-
descriptionEn?: string;
|
|
24
|
-
|
|
25
8
|
@IsOptional()
|
|
26
9
|
@IsEnum(['active', 'inactive'])
|
|
27
10
|
status?: 'active' | 'inactive';
|
|
@@ -6,24 +6,6 @@ export class UpdateInstructorSkillDto {
|
|
|
6
6
|
@MaxLength(255)
|
|
7
7
|
slug?: string;
|
|
8
8
|
|
|
9
|
-
@IsOptional()
|
|
10
|
-
@IsString()
|
|
11
|
-
@MaxLength(255)
|
|
12
|
-
namePt?: string;
|
|
13
|
-
|
|
14
|
-
@IsOptional()
|
|
15
|
-
@IsString()
|
|
16
|
-
@MaxLength(255)
|
|
17
|
-
nameEn?: string;
|
|
18
|
-
|
|
19
|
-
@IsOptional()
|
|
20
|
-
@IsString()
|
|
21
|
-
descriptionPt?: string;
|
|
22
|
-
|
|
23
|
-
@IsOptional()
|
|
24
|
-
@IsString()
|
|
25
|
-
descriptionEn?: string;
|
|
26
|
-
|
|
27
9
|
@IsOptional()
|
|
28
10
|
@IsEnum(['active', 'inactive'])
|
|
29
11
|
status?: 'active' | 'inactive';
|
|
@@ -11,10 +11,6 @@ export class InstructorSkillService {
|
|
|
11
11
|
return (this.prisma as any).instructor_skill;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
private get localeClient() {
|
|
15
|
-
return (this.prisma as any).instructor_skill_locale;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
14
|
async list(params: { page?: number; pageSize?: number; search?: string }) {
|
|
19
15
|
const page = Math.max(Number(params.page) || 1, 1);
|
|
20
16
|
const pageSize = Math.max(Number(params.pageSize) || 20, 1);
|
|
@@ -24,14 +20,7 @@ export class InstructorSkillService {
|
|
|
24
20
|
const where: any = {};
|
|
25
21
|
|
|
26
22
|
if (search) {
|
|
27
|
-
where.
|
|
28
|
-
{ slug: { contains: search, mode: 'insensitive' } },
|
|
29
|
-
{
|
|
30
|
-
instructor_skill_locale: {
|
|
31
|
-
some: { name: { contains: search, mode: 'insensitive' } },
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
];
|
|
23
|
+
where.slug = { contains: search, mode: 'insensitive' };
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
const [rows, total] = await Promise.all([
|
|
@@ -39,11 +28,6 @@ export class InstructorSkillService {
|
|
|
39
28
|
where,
|
|
40
29
|
skip,
|
|
41
30
|
take: pageSize,
|
|
42
|
-
include: {
|
|
43
|
-
instructor_skill_locale: {
|
|
44
|
-
select: { locale_id: true, name: true, description: true },
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
31
|
orderBy: { slug: 'asc' },
|
|
48
32
|
}),
|
|
49
33
|
this.skillClient.count({ where }),
|
|
@@ -60,11 +44,6 @@ export class InstructorSkillService {
|
|
|
60
44
|
async getAll() {
|
|
61
45
|
const rows = await this.skillClient.findMany({
|
|
62
46
|
where: { status: 'active' },
|
|
63
|
-
include: {
|
|
64
|
-
instructor_skill_locale: {
|
|
65
|
-
select: { locale_id: true, name: true },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
47
|
orderBy: { slug: 'asc' },
|
|
69
48
|
});
|
|
70
49
|
|
|
@@ -79,8 +58,6 @@ export class InstructorSkillService {
|
|
|
79
58
|
},
|
|
80
59
|
});
|
|
81
60
|
|
|
82
|
-
await this.syncLocales(skill.id, dto);
|
|
83
|
-
|
|
84
61
|
return this.getSkillById(skill.id);
|
|
85
62
|
}
|
|
86
63
|
|
|
@@ -103,8 +80,6 @@ export class InstructorSkillService {
|
|
|
103
80
|
await this.skillClient.update({ where: { id }, data });
|
|
104
81
|
}
|
|
105
82
|
|
|
106
|
-
await this.syncLocales(id, dto);
|
|
107
|
-
|
|
108
83
|
return this.getSkillById(id);
|
|
109
84
|
}
|
|
110
85
|
|
|
@@ -124,11 +99,6 @@ export class InstructorSkillService {
|
|
|
124
99
|
private async getSkillById(id: number) {
|
|
125
100
|
const row = await this.skillClient.findUnique({
|
|
126
101
|
where: { id },
|
|
127
|
-
include: {
|
|
128
|
-
instructor_skill_locale: {
|
|
129
|
-
select: { locale_id: true, name: true, description: true },
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
102
|
});
|
|
133
103
|
|
|
134
104
|
if (!row) {
|
|
@@ -143,72 +113,7 @@ export class InstructorSkillService {
|
|
|
143
113
|
id: row.id,
|
|
144
114
|
slug: row.slug,
|
|
145
115
|
status: row.status,
|
|
146
|
-
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
private async syncLocales(
|
|
151
|
-
skillId: number,
|
|
152
|
-
dto: CreateInstructorSkillDto | UpdateInstructorSkillDto,
|
|
153
|
-
) {
|
|
154
|
-
const localeIds = await this.resolveLocaleIds();
|
|
155
|
-
|
|
156
|
-
if (dto.namePt !== undefined && localeIds.pt !== null) {
|
|
157
|
-
const existing = await this.localeClient.findFirst({
|
|
158
|
-
where: { instructor_skill_id: skillId, locale_id: localeIds.pt },
|
|
159
|
-
select: { id: true },
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
if (existing) {
|
|
163
|
-
await this.localeClient.update({
|
|
164
|
-
where: { id: existing.id },
|
|
165
|
-
data: { name: dto.namePt, description: dto.descriptionPt ?? null },
|
|
166
|
-
});
|
|
167
|
-
} else {
|
|
168
|
-
await this.localeClient.create({
|
|
169
|
-
data: {
|
|
170
|
-
instructor_skill_id: skillId,
|
|
171
|
-
locale_id: localeIds.pt,
|
|
172
|
-
name: dto.namePt,
|
|
173
|
-
description: dto.descriptionPt ?? null,
|
|
174
|
-
},
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (dto.nameEn !== undefined && localeIds.en !== null) {
|
|
180
|
-
const existing = await this.localeClient.findFirst({
|
|
181
|
-
where: { instructor_skill_id: skillId, locale_id: localeIds.en },
|
|
182
|
-
select: { id: true },
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (existing) {
|
|
186
|
-
await this.localeClient.update({
|
|
187
|
-
where: { id: existing.id },
|
|
188
|
-
data: { name: dto.nameEn, description: dto.descriptionEn ?? null },
|
|
189
|
-
});
|
|
190
|
-
} else {
|
|
191
|
-
await this.localeClient.create({
|
|
192
|
-
data: {
|
|
193
|
-
instructor_skill_id: skillId,
|
|
194
|
-
locale_id: localeIds.en,
|
|
195
|
-
name: dto.nameEn,
|
|
196
|
-
description: dto.descriptionEn ?? null,
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
private async resolveLocaleIds() {
|
|
204
|
-
const locales = await this.prisma.locale.findMany({
|
|
205
|
-
where: { code: { in: ['pt', 'en'] } },
|
|
206
|
-
select: { id: true, code: true },
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
pt: locales.find((l) => l.code === 'pt')?.id ?? null,
|
|
211
|
-
en: locales.find((l) => l.code === 'en')?.id ?? null,
|
|
116
|
+
name: row.slug,
|
|
212
117
|
};
|
|
213
118
|
}
|
|
214
119
|
}
|
|
@@ -53,6 +53,22 @@ export class InstructorController {
|
|
|
53
53
|
return this.instructorService.create(dto);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
@Get(':id/class-groups')
|
|
57
|
+
getInstructorClassGroups(
|
|
58
|
+
@Param('id', ParseIntPipe) id: number,
|
|
59
|
+
@Query('page', new ParseIntPipe({ optional: true })) page?: number,
|
|
60
|
+
@Query('pageSize', new ParseIntPipe({ optional: true })) pageSize?: number,
|
|
61
|
+
@Query('search') search?: string,
|
|
62
|
+
@Query('status') status?: string,
|
|
63
|
+
) {
|
|
64
|
+
return this.instructorService.getInstructorClassGroups(id, {
|
|
65
|
+
page,
|
|
66
|
+
pageSize,
|
|
67
|
+
search,
|
|
68
|
+
status,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
@Get(':id')
|
|
57
73
|
getById(@Param('id', ParseIntPipe) id: number) {
|
|
58
74
|
return this.instructorService.getById(id);
|
|
@@ -102,6 +102,7 @@ export class InstructorService {
|
|
|
102
102
|
user_id: true,
|
|
103
103
|
user: {
|
|
104
104
|
select: {
|
|
105
|
+
photo_id: true,
|
|
105
106
|
role_user: {
|
|
106
107
|
where: { role: { slug: 'lms-instructor' } },
|
|
107
108
|
select: { id: true },
|
|
@@ -146,10 +147,6 @@ export class InstructorService {
|
|
|
146
147
|
select: {
|
|
147
148
|
id: true,
|
|
148
149
|
slug: true,
|
|
149
|
-
instructor_skill_locale: {
|
|
150
|
-
take: 1,
|
|
151
|
-
select: { name: true },
|
|
152
|
-
},
|
|
153
150
|
},
|
|
154
151
|
},
|
|
155
152
|
},
|
|
@@ -170,7 +167,7 @@ export class InstructorService {
|
|
|
170
167
|
existing.push({
|
|
171
168
|
id: a.instructor_skill.id,
|
|
172
169
|
slug: a.instructor_skill.slug,
|
|
173
|
-
name: a.instructor_skill.
|
|
170
|
+
name: a.instructor_skill.slug,
|
|
174
171
|
});
|
|
175
172
|
skillsMap.set(a.instructor_id, existing);
|
|
176
173
|
}
|
|
@@ -185,6 +182,7 @@ export class InstructorService {
|
|
|
185
182
|
personId: row.person_id,
|
|
186
183
|
name: row.person?.name?.trim() || `Instructor #${row.id}`,
|
|
187
184
|
avatarId: row.person?.avatar_id ?? null,
|
|
185
|
+
userPhotoId: personUser?.user?.photo_id ?? null,
|
|
188
186
|
email: this.getPrimaryContactValue(
|
|
189
187
|
row.person?.contact ?? [],
|
|
190
188
|
['EMAIL'],
|
|
@@ -470,10 +468,6 @@ export class InstructorService {
|
|
|
470
468
|
select: {
|
|
471
469
|
id: true,
|
|
472
470
|
slug: true,
|
|
473
|
-
instructor_skill_locale: {
|
|
474
|
-
take: 1,
|
|
475
|
-
select: { name: true },
|
|
476
|
-
},
|
|
477
471
|
},
|
|
478
472
|
},
|
|
479
473
|
},
|
|
@@ -495,7 +489,7 @@ export class InstructorService {
|
|
|
495
489
|
.map((a) => ({
|
|
496
490
|
id: a.instructor_skill.id,
|
|
497
491
|
slug: a.instructor_skill.slug,
|
|
498
|
-
name: a.instructor_skill.
|
|
492
|
+
name: a.instructor_skill.slug,
|
|
499
493
|
}));
|
|
500
494
|
|
|
501
495
|
return {
|
|
@@ -924,4 +918,87 @@ export class InstructorService {
|
|
|
924
918
|
});
|
|
925
919
|
}
|
|
926
920
|
}
|
|
921
|
+
|
|
922
|
+
async getInstructorClassGroups(
|
|
923
|
+
instructorId: number,
|
|
924
|
+
options: {
|
|
925
|
+
page?: number;
|
|
926
|
+
pageSize?: number;
|
|
927
|
+
search?: string;
|
|
928
|
+
status?: string;
|
|
929
|
+
} = {},
|
|
930
|
+
) {
|
|
931
|
+
const { page = 1, pageSize = 6, search, status } = options;
|
|
932
|
+
|
|
933
|
+
const where: Prisma.course_class_groupWhereInput = {
|
|
934
|
+
instructor_id: instructorId,
|
|
935
|
+
...(status ? { status: status as any } : {}),
|
|
936
|
+
...(search
|
|
937
|
+
? {
|
|
938
|
+
OR: [
|
|
939
|
+
{ title: { contains: search, mode: 'insensitive' } },
|
|
940
|
+
{ code: { contains: search, mode: 'insensitive' } },
|
|
941
|
+
{ course: { title: { contains: search, mode: 'insensitive' } } },
|
|
942
|
+
],
|
|
943
|
+
}
|
|
944
|
+
: {}),
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
const [classGroups, total] = await this.prisma.$transaction([
|
|
948
|
+
this.prisma.course_class_group.findMany({
|
|
949
|
+
where,
|
|
950
|
+
orderBy: { start_date: 'desc' },
|
|
951
|
+
include: {
|
|
952
|
+
course: {
|
|
953
|
+
select: {
|
|
954
|
+
id: true,
|
|
955
|
+
title: true,
|
|
956
|
+
course_image: {
|
|
957
|
+
where: {
|
|
958
|
+
image_type: { slug: { in: ['course-logo', 'course-banner'] } },
|
|
959
|
+
},
|
|
960
|
+
orderBy: { is_primary: 'desc' },
|
|
961
|
+
select: {
|
|
962
|
+
file: { select: { id: true, location: true } },
|
|
963
|
+
image_type: { select: { slug: true } },
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
_count: {
|
|
969
|
+
select: {
|
|
970
|
+
course_enrollment: { where: { status: { not: 'cancelled' } } },
|
|
971
|
+
},
|
|
972
|
+
},
|
|
973
|
+
},
|
|
974
|
+
skip: (page - 1) * pageSize,
|
|
975
|
+
take: pageSize,
|
|
976
|
+
}),
|
|
977
|
+
this.prisma.course_class_group.count({ where }),
|
|
978
|
+
]);
|
|
979
|
+
|
|
980
|
+
const data = classGroups.map((cg) => {
|
|
981
|
+
const preferredImage =
|
|
982
|
+
cg.course?.course_image.find(
|
|
983
|
+
(ci) => ci.image_type?.slug === 'course-logo',
|
|
984
|
+
) ?? cg.course?.course_image[0];
|
|
985
|
+
|
|
986
|
+
return {
|
|
987
|
+
id: cg.id,
|
|
988
|
+
name: cg.title,
|
|
989
|
+
code: cg.code ?? '',
|
|
990
|
+
courseId: cg.course?.id ?? null,
|
|
991
|
+
courseName: cg.course?.title ?? null,
|
|
992
|
+
courseLogoFileId: preferredImage?.file?.id ?? null,
|
|
993
|
+
courseLogoUrl: preferredImage?.file?.location ?? null,
|
|
994
|
+
startDate: cg.start_date,
|
|
995
|
+
endDate: cg.end_date ?? null,
|
|
996
|
+
slots: cg._count.course_enrollment,
|
|
997
|
+
totalSlots: cg.capacity ?? null,
|
|
998
|
+
status: cg.status,
|
|
999
|
+
};
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
return { data, total, page, pageSize, lastPage: Math.ceil(total / pageSize) };
|
|
1003
|
+
}
|
|
927
1004
|
}
|
package/src/lms.module.ts
CHANGED