New_Micro_Learning/backend/database/seeders/DatabaseSeeder.php

265 خطوط
18 KiB
PHP

<?php
namespace Database\Seeders;
use App\Models\User;
use App\Modules\Analytics\Application\AnalyticsProjectionService;
use App\Modules\Assessments\Domain\Assessment;
use App\Modules\Assessments\Domain\AssessmentAttempt;
use App\Modules\Assessments\Domain\Question;
use App\Modules\Assessments\Domain\QuestionResult;
use App\Modules\Assignments\Application\AssignmentResolver;
use App\Modules\Assignments\Domain\Assignment;
use App\Modules\Courses\Domain\Block;
use App\Modules\Courses\Domain\Course;
use App\Modules\Courses\Domain\CourseModule;
use App\Modules\Courses\Domain\CourseVersion;
use App\Modules\Courses\Domain\Enums\CourseVersionStatus;
use App\Modules\Courses\Domain\Lesson;
use App\Modules\Identity\Domain\Enums\AccountStatus;
use App\Modules\Identity\Domain\Enums\UserRole;
use App\Modules\Learner\Domain\LearningEvent;
use App\Modules\Monitoring\Application\MonitoringEngine;
use App\Modules\Organizations\Domain\Organization;
use App\Modules\Subscriptions\Domain\Subscription;
use App\Modules\Teams\Domain\Team;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
$this->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;
}
}