173 lines
5.8 KiB
PHP
173 lines
5.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Academy;
|
|
|
|
use App\Models\AcademyCourse;
|
|
use App\Models\AcademyCourseEnrollment;
|
|
use App\Models\AcademyCourseLesson;
|
|
use App\Models\AcademyLesson;
|
|
use App\Models\AcademyLessonProgress;
|
|
use App\Models\User;
|
|
|
|
final class AcademyCourseProgressService
|
|
{
|
|
public function __construct(private readonly AcademyCourseNavigationService $navigation)
|
|
{
|
|
}
|
|
|
|
public function getProgress(?User $user, AcademyCourse $course): array
|
|
{
|
|
$totalRequired = $this->getTotalRequiredLessonsCount($course);
|
|
$completedRequired = $user ? $this->getCompletedRequiredLessonsCount($user, $course) : 0;
|
|
$enrollment = $user
|
|
? AcademyCourseEnrollment::query()->with('lastLesson')->where('user_id', $user->id)->where('course_id', $course->id)->first()
|
|
: null;
|
|
|
|
return [
|
|
'total_required' => $totalRequired,
|
|
'completed_required' => $completedRequired,
|
|
'progress_percent' => $this->getProgressPercent($user, $course),
|
|
'enrollment' => $enrollment,
|
|
'next_lesson' => $user ? $this->getNextLesson($user, $course) : null,
|
|
'continue_lesson' => $user ? $this->getContinueLesson($user, $course) : null,
|
|
'completed' => $enrollment?->status === AcademyCourseEnrollment::STATUS_COMPLETED,
|
|
];
|
|
}
|
|
|
|
public function getCompletedRequiredLessonsCount(User $user, AcademyCourse $course): int
|
|
{
|
|
$lessonIds = $course->courseLessons()
|
|
->where('is_required', true)
|
|
->pluck('lesson_id')
|
|
->filter()
|
|
->values()
|
|
->all();
|
|
|
|
if ($lessonIds === []) {
|
|
return 0;
|
|
}
|
|
|
|
return AcademyLessonProgress::query()
|
|
->where('user_id', $user->id)
|
|
->whereIn('lesson_id', $lessonIds)
|
|
->whereNotNull('completed_at')
|
|
->count();
|
|
}
|
|
|
|
public function getCompletedLessonIds(User $user, AcademyCourse $course): array
|
|
{
|
|
$lessonIds = $course->courseLessons()
|
|
->pluck('lesson_id')
|
|
->filter()
|
|
->map(fn ($lessonId): int => (int) $lessonId)
|
|
->values()
|
|
->all();
|
|
|
|
if ($lessonIds === []) {
|
|
return [];
|
|
}
|
|
|
|
return AcademyLessonProgress::query()
|
|
->where('user_id', $user->id)
|
|
->whereIn('lesson_id', $lessonIds)
|
|
->whereNotNull('completed_at')
|
|
->pluck('lesson_id')
|
|
->map(fn ($lessonId): int => (int) $lessonId)
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
public function getTotalRequiredLessonsCount(AcademyCourse $course): int
|
|
{
|
|
return $course->courseLessons()->where('is_required', true)->count();
|
|
}
|
|
|
|
public function getProgressPercent(?User $user, AcademyCourse $course): int
|
|
{
|
|
if (! $user) {
|
|
return 0;
|
|
}
|
|
|
|
$totalRequired = $this->getTotalRequiredLessonsCount($course);
|
|
|
|
if ($totalRequired < 1) {
|
|
return 0;
|
|
}
|
|
|
|
return (int) floor(($this->getCompletedRequiredLessonsCount($user, $course) / $totalRequired) * 100);
|
|
}
|
|
|
|
public function getNextLesson(User $user, AcademyCourse $course): ?AcademyCourseLesson
|
|
{
|
|
$completedLessonIds = $this->getCompletedLessonIds($user, $course);
|
|
|
|
return $this->navigation->orderedCourseLessons($course)
|
|
->first(fn (AcademyCourseLesson $courseLesson): bool => ! in_array((int) $courseLesson->lesson_id, $completedLessonIds, true));
|
|
}
|
|
|
|
public function getPreviousLesson(AcademyCourse $course, AcademyLesson $lesson): ?AcademyCourseLesson
|
|
{
|
|
return $this->navigation->previousLesson($course, $lesson);
|
|
}
|
|
|
|
public function getContinueLesson(User $user, AcademyCourse $course): ?AcademyCourseLesson
|
|
{
|
|
$enrollment = AcademyCourseEnrollment::query()
|
|
->where('user_id', $user->id)
|
|
->where('course_id', $course->id)
|
|
->first();
|
|
|
|
if ($enrollment?->last_lesson_id) {
|
|
$lastLesson = AcademyLesson::query()->find($enrollment->last_lesson_id);
|
|
|
|
if ($lastLesson instanceof AcademyLesson) {
|
|
$nextLesson = $this->navigation->nextLesson($course, $lastLesson);
|
|
|
|
return $nextLesson ?? $this->navigation->findCourseLesson($course, $lastLesson);
|
|
}
|
|
}
|
|
|
|
return $this->getNextLesson($user, $course) ?? $this->navigation->firstPublishedLesson($course);
|
|
}
|
|
|
|
public function markEnrollmentStarted(User $user, AcademyCourse $course): AcademyCourseEnrollment
|
|
{
|
|
return AcademyCourseEnrollment::query()->updateOrCreate(
|
|
[
|
|
'user_id' => $user->id,
|
|
'course_id' => $course->id,
|
|
],
|
|
[
|
|
'status' => AcademyCourseEnrollment::STATUS_ACTIVE,
|
|
'started_at' => now(),
|
|
'completed_at' => null,
|
|
],
|
|
);
|
|
}
|
|
|
|
public function updateLastLesson(User $user, AcademyCourse $course, AcademyLesson $lesson): AcademyCourseEnrollment
|
|
{
|
|
$enrollment = $this->markEnrollmentStarted($user, $course);
|
|
$enrollment->forceFill([
|
|
'last_lesson_id' => $lesson->id,
|
|
])->save();
|
|
|
|
return $enrollment;
|
|
}
|
|
|
|
public function markCourseCompletedIfFinished(User $user, AcademyCourse $course): AcademyCourseEnrollment
|
|
{
|
|
$enrollment = $this->markEnrollmentStarted($user, $course);
|
|
$progressPercent = $this->getProgressPercent($user, $course);
|
|
$isComplete = $this->getTotalRequiredLessonsCount($course) > 0 && $progressPercent >= 100;
|
|
|
|
$enrollment->forceFill([
|
|
'status' => $isComplete ? AcademyCourseEnrollment::STATUS_COMPLETED : AcademyCourseEnrollment::STATUS_ACTIVE,
|
|
'completed_at' => $isComplete ? ($enrollment->completed_at ?? now()) : null,
|
|
])->save();
|
|
|
|
return $enrollment;
|
|
}
|
|
} |