chore: commit remaining workspace changes

This commit is contained in:
2026-05-08 21:51:29 +02:00
parent 8d108b8a76
commit ff96ef796e
97 changed files with 18020 additions and 2196 deletions

View File

@@ -0,0 +1,173 @@
<?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;
}
}