user(null, 'مدیر سامانه', 'admin@microlearn.test', UserRole::SuperAdmin); $organization = Organization::query()->updateOrCreate(['slug' => 'safe-academy'], [ 'name' => 'آکادمی ایمن', 'status' => 'active', 'default_locale' => 'fa', 'timezone' => 'Asia/Tehran', ]); $designer = $this->user($organization->getKey(), 'سارا محمدی', 'designer@microlearn.test', UserRole::CourseDesigner); $manager = $this->user($organization->getKey(), 'علی رضایی', 'manager@microlearn.test', UserRole::Manager); $learners = collect([ $this->user($organization->getKey(), 'مریم احمدی', 'maryam@microlearn.test', UserRole::Learner), $this->user($organization->getKey(), 'امیر حسینی', 'amir@microlearn.test', UserRole::Learner), $this->user($organization->getKey(), 'نازنین کریمی', 'nazanin@microlearn.test', UserRole::Learner), ]); $manager->update(['first_name' => 'علی', 'last_name' => 'رضایی', 'department' => 'عملیات', 'job_level' => 'manager']); $profiles = [ 'maryam@microlearn.test' => ['مریم', 'احمدی', 'کارشناس'], 'amir@microlearn.test' => ['امیر', 'حسینی', 'کارشناس'], 'nazanin@microlearn.test' => ['نازنین', 'کریمی', 'کارشناس'], ]; $learners->each(function (User $learner) use ($manager, $profiles): void { [$firstName, $lastName] = $profiles[$learner->email]; $learner->update([ 'first_name' => $firstName, 'last_name' => $lastName, 'department' => 'عملیات', 'job_level' => 'specialist', 'direct_manager_id' => $manager->getKey(), ]); }); $team = Team::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'name' => 'عملیات'], ['description' => 'تیم عملیات و ایمنی محیط کار', 'status' => 'active', 'created_by' => $designer->getKey()], ); $team->managers()->syncWithoutDetaching([$manager->getKey()]); $team->members()->syncWithoutDetaching($learners->pluck('id')->all()); Subscription::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'plan_key' => 'enterprise'], [ 'status' => 'active', 'starts_at' => now()->startOfMonth(), 'expires_at' => now()->addYear(), 'seat_limit' => 100, 'storage_quota_bytes' => 107374182400, 'storage_used_bytes' => 28991029248, 'ai_credit_quota' => 10000, 'ai_credits_used' => 2750, 'enabled_features' => ['taxonomy', 'advanced_analytics', 'exports', 'certificates'], ], ); $course = Course::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'slug' => 'workplace-safety'], ['title' => 'ایمنی در محیط کار', 'status' => 'draft', 'created_by' => $designer->getKey()], ); $published = CourseVersion::query()->firstOrCreate( ['course_id' => $course->getKey(), 'version_number' => 1], [ 'organization_id' => $organization->getKey(), 'status' => CourseVersionStatus::Published, 'title' => 'ایمنی در محیط کار', 'description' => 'نسخه پایه منتشرشده برای آموزش ایمنی کارکنان.', 'settings' => ['language' => 'fa', 'difficulty' => 'beginner'], 'published_at' => now()->subMonth(), ], ); $publishedModule = CourseModule::query()->updateOrCreate( ['course_version_id' => $published->getKey(), 'position' => 1], ['organization_id' => $organization->getKey(), 'title' => 'مبانی ایمنی'], ); $publishedLesson = Lesson::query()->updateOrCreate( ['course_module_id' => $publishedModule->getKey(), 'position' => 1], ['organization_id' => $organization->getKey(), 'course_version_id' => $published->getKey(), 'title' => 'استفاده صحیح از تجهیزات حفاظتی', 'presentation_mode' => 'flow'], ); Block::query()->updateOrCreate( ['lesson_id' => $publishedLesson->getKey(), 'position' => 1], ['organization_id' => $organization->getKey(), 'course_version_id' => $published->getKey(), 'type' => 'heading', 'schema_version' => 1, 'data' => ['text' => 'ایمنی مسئولیت همه ماست', 'level' => 2], 'revision' => 1], ); Block::query()->updateOrCreate( ['lesson_id' => $publishedLesson->getKey(), 'position' => 2], ['organization_id' => $organization->getKey(), 'course_version_id' => $published->getKey(), 'type' => 'key_point', 'schema_version' => 1, 'data' => ['title' => 'نکته کلیدی', 'body' => 'پیش از شروع کار، تجهیزات حفاظت فردی را بررسی و به‌درستی استفاده کنید.'], 'revision' => 1], ); $draft = CourseVersion::query()->updateOrCreate( ['course_id' => $course->getKey(), 'version_number' => 2], [ 'organization_id' => $organization->getKey(), 'source_version_id' => $published->getKey(), 'status' => CourseVersionStatus::Draft, 'title' => 'ایمنی در محیط کار', 'description' => 'بازنگری دوره با تمرکز بر تجهیزات حفاظت فردی و گزارش خطر.', 'settings' => ['language' => 'fa', 'difficulty' => 'beginner'], ], ); $basics = CourseModule::query()->updateOrCreate( ['course_version_id' => $draft->getKey(), 'position' => 1], ['organization_id' => $organization->getKey(), 'title' => 'مبانی ایمنی'], ); $response = CourseModule::query()->updateOrCreate( ['course_version_id' => $draft->getKey(), 'position' => 2], ['organization_id' => $organization->getKey(), 'title' => 'واکنش و گزارش‌دهی'], ); foreach ([[$basics, 1, 'شناخت خطرهای محیط کار'], [$basics, 2, 'تجهیزات حفاظت فردی'], [$response, 1, 'گزارش رویداد و خطر']] as [$module, $position, $title]) { Lesson::query()->updateOrCreate( ['course_module_id' => $module->getKey(), 'position' => $position], ['organization_id' => $organization->getKey(), 'course_version_id' => $draft->getKey(), 'title' => $title, 'presentation_mode' => 'flow'], ); } $bankQuestion = Question::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'is_bank_item' => true, 'prompt' => 'اولین اقدام پس از مشاهده خطر در محیط کار چیست؟'], [ 'type' => 'single_choice', 'configuration' => ['options' => [ ['id' => 'report', 'text' => 'ایمن‌سازی محل و گزارش خطر', 'correct' => true, 'feedback' => 'درست است.'], ['id' => 'ignore', 'text' => 'نادیده گرفتن تا پایان شیفت', 'correct' => false, 'feedback' => 'خطر باید فوری گزارش شود.'], ]], 'difficulty' => 'beginner', 'position' => 0, 'schema_version' => 1, 'topic' => 'گزارش خطر', 'tags' => ['ایمنی', 'گزارش‌دهی'], 'explanation' => 'ابتدا محیط را ایمن و سپس خطر را طبق فرایند سازمان گزارش کنید.', ], ); Question::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'is_bank_item' => true, 'prompt' => 'در زمان نشت ماده ناشناخته چه تصمیمی می‌گیرید؟'], [ 'type' => 'scenario', 'configuration' => ['context' => 'در انبار بوی نامعمول و ظرف آسیب‌دیده مشاهده می‌کنید.', 'choices' => [ ['text' => 'فاصله گرفتن، محدودکردن دسترسی و اطلاع به مسئول ایمنی', 'score' => 1, 'feedback' => 'انتخاب ایمن و مطابق رویه است.'], ['text' => 'نزدیک شدن و بررسی ماده بدون تجهیزات', 'score' => 0, 'feedback' => 'این اقدام مواجهه را افزایش می‌دهد.'], ]], 'difficulty' => 'intermediate', 'position' => 0, 'schema_version' => 1, 'topic' => 'واکنش اضطراری', 'tags' => ['سناریو', 'تصمیم‌گیری'], 'explanation' => 'در مواجهه با ماده ناشناخته، کنترل محدوده و گزارش فوری اولویت دارد.', ], ); $learnerAssignment = Assignment::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'assignable_type' => 'course', 'assignable_id' => $published->getKey(), 'target_type' => 'team', 'target_id' => $team->getKey()], ['status' => 'active', 'mandatory' => true, 'due_at' => now()->addDays(14), 'reminder_days' => 3, 'escalation_policy' => ['enabled' => true], 'source' => 'seed', 'assigned_by' => $designer->getKey()], ); app(AssignmentResolver::class)->sync($learnerAssignment); $progressProfiles = [ 'maryam@microlearn.test' => ['progress' => 75, 'status' => 'in_progress', 'completed_at' => null], 'amir@microlearn.test' => ['progress' => 100, 'status' => 'completed', 'completed_at' => now()->subDays(2)], 'nazanin@microlearn.test' => ['progress' => 25, 'status' => 'in_progress', 'completed_at' => null], ]; foreach ($learners as $learner) { DB::table('assignment_users')->where('assignment_id', $learnerAssignment->getKey())->where('user_id', $learner->getKey())->update([...$progressProfiles[$learner->email], 'updated_at' => now()]); } $publishedBlocks = Block::query()->where('course_version_id', $published->getKey())->orderBy('position')->get(); $eventRows = [ [$learners[0], '11111111-1111-4111-8111-111111111101', 'course.opened', null, null, 10, 120], [$learners[0], '11111111-1111-4111-8111-111111111102', 'lesson.started', $publishedLesson, null, 9, 60], [$learners[0], '11111111-1111-4111-8111-111111111103', 'block.completed', $publishedLesson, $publishedBlocks[0], 8, 240], [$learners[1], '22222222-2222-4222-8222-222222222201', 'course.opened', null, null, 7, 90], [$learners[1], '22222222-2222-4222-8222-222222222202', 'lesson.started', $publishedLesson, null, 6, 60], [$learners[1], '22222222-2222-4222-8222-222222222203', 'lesson.completed', $publishedLesson, null, 2, 420], [$learners[1], '22222222-2222-4222-8222-222222222204', 'course.completed', null, null, 2, 30], [$learners[2], '33333333-3333-4333-8333-333333333301', 'course.opened', null, null, 14, 45], [$learners[2], '33333333-3333-4333-8333-333333333302', 'lesson.started', $publishedLesson, null, 14, 30], ]; foreach ($eventRows as [$learner, $clientId, $type, $lesson, $block, $daysAgo, $duration]) { LearningEvent::query()->firstOrCreate(['learner_id' => $learner->getKey(), 'client_event_id' => $clientId], [ 'organization_id' => $organization->getKey(), 'event_type' => $type, 'schema_version' => 1, 'session_id' => 'seed-'.$learner->getKey(), 'correlation_id' => $clientId, 'device_context' => ['platform' => 'web', 'formFactor' => 'desktop', 'online' => true], 'assignment_id' => $learnerAssignment->getKey(), 'course_version_id' => $published->getKey(), 'lesson_id' => $lesson?->getKey(), 'block_id' => $block?->getKey(), 'payload' => ['durationSeconds' => $duration], 'occurred_at' => now()->subDays($daysAgo), 'received_at' => now()->subDays($daysAgo)->addMinute(), ]); } $publishedAssessment = Assessment::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'course_version_id' => $published->getKey(), 'title' => 'آزمون کوتاه ایمنی'], ['lesson_id' => $publishedLesson->getKey(), 'settings' => ['passingScore' => 70, 'attemptLimit' => 2]], ); $publishedQuestion = Question::query()->updateOrCreate( ['assessment_id' => $publishedAssessment->getKey(), 'prompt' => 'پیش از شروع کار چه اقدامی ضروری است؟'], ['organization_id' => $organization->getKey(), 'course_version_id' => $published->getKey(), 'is_bank_item' => false, 'type' => 'single_choice', 'configuration' => ['options' => [['text' => 'بررسی تجهیزات حفاظت فردی', 'correct' => true], ['text' => 'شروع فوری کار', 'correct' => false]]], 'difficulty' => 'beginner', 'position' => 1, 'schema_version' => 1], ); foreach ([[$learners[0], .85, true], [$learners[1], .72, true], [$learners[2], .45, false]] as [$learner, $score, $correct]) { $attempt = AssessmentAttempt::query()->updateOrCreate( ['learner_id' => $learner->getKey(), 'assessment_id' => $publishedAssessment->getKey(), 'attempt_number' => 1], ['organization_id' => $organization->getKey(), 'course_version_id' => $published->getKey(), 'status' => 'completed', 'score' => $score, 'started_at' => now()->subDays(5), 'completed_at' => now()->subDays(5)->addMinutes(5)], ); QuestionResult::query()->updateOrCreate( ['assessment_attempt_id' => $attempt->getKey(), 'question_id' => $publishedQuestion->getKey()], ['organization_id' => $organization->getKey(), 'raw_value' => [$correct ? 0 : 1], 'normalized_value' => $score, 'is_correct' => $correct, 'occurred_at' => $attempt->completed_at, 'metadata' => ['source' => 'seed-review']], ); } $assessment = Assessment::query()->updateOrCreate( ['organization_id' => $organization->getKey(), 'course_version_id' => $draft->getKey(), 'title' => 'ارزیابی مبانی ایمنی'], [ 'lesson_id' => Lesson::query()->where('course_version_id', $draft->getKey())->orderBy('position')->value('id'), 'settings' => ['randomSelection' => false, 'questionPoolSize' => null, 'shuffleQuestions' => true, 'shuffleOptions' => true, 'passingScore' => 70, 'attemptLimit' => 2, 'feedbackMode' => 'after_submission', 'timeLimitSeconds' => 300], ], ); Question::query()->updateOrCreate( ['assessment_id' => $assessment->getKey(), 'source_question_id' => $bankQuestion->getKey()], [ 'organization_id' => $organization->getKey(), 'course_version_id' => $draft->getKey(), 'is_bank_item' => false, 'type' => $bankQuestion->type, 'prompt' => $bankQuestion->prompt, 'configuration' => $bankQuestion->configuration, 'difficulty' => $bankQuestion->difficulty, 'position' => 1, 'schema_version' => 1, 'topic' => $bankQuestion->topic, 'tags' => $bankQuestion->tags, 'explanation' => $bankQuestion->explanation, ], ); app(AnalyticsProjectionService::class)->rebuild($organization->getKey()); app(MonitoringEngine::class)->refresh($organization->getKey()); } private function user(?string $organizationId, string $name, string $email, UserRole $role): User { $attributes = [ 'organization_id' => $organizationId, 'name' => $name, 'password' => 'password', 'email_verified_at' => now(), 'role' => $role, 'status' => AccountStatus::Active, 'locale' => 'fa', 'timezone' => 'Asia/Tehran', ]; $user = User::query()->where('email', $email)->first() ?? new User(['email' => $email]); if (! $user->exists && Schema::getColumnType('users', 'id') === 'integer') { $user->forceFill(['id' => ((int) User::query()->max('id')) + 1]); } $user->fill($attributes)->save(); return $user; } }