Perbaikan modul data karyawan dan staff + modul SOP import
This commit is contained in:
@@ -22,11 +22,11 @@ class MigrateLmsData extends Command
|
||||
$this->migrateDepartmentPositions();
|
||||
|
||||
// 2. Eksekusi Data User
|
||||
$this->migrateStaffToUsers();
|
||||
$this->migrateStudentsToUsers();
|
||||
// $this->migrateStaffToUsers();
|
||||
// $this->migrateStudentsToUsers();
|
||||
|
||||
// 3. Sinkronisasi Departemen & Posisi ke User
|
||||
$this->syncUserDepartmentAndPosition();
|
||||
// $this->syncUserDepartmentAndPosition();
|
||||
|
||||
// 4. Eksekusi Ujian & Hasil
|
||||
$this->migrateQuestions();
|
||||
@@ -205,30 +205,79 @@ class MigrateLmsData extends Command
|
||||
/**
|
||||
* TAHAP 4A: Memindahkan data Bank Soal
|
||||
*/
|
||||
private function migrateQuestions()
|
||||
protected function migrateQuestions()
|
||||
{
|
||||
$this->warn('4A. Menarik data Bank Soal...');
|
||||
$oldQuestions = DB::connection('mysql_old')->table('questions')->get();
|
||||
$this->info('4A. Menarik data Bank Soal...');
|
||||
|
||||
// Pastikan Master Subject ditarik terlebih dahulu agar tidak ada yang terlewat
|
||||
$oldSubjects = DB::table('lmsv2-old.subjects')->get();
|
||||
foreach ($oldSubjects as $os) {
|
||||
\App\Models\Subject::updateOrCreate(
|
||||
['id' => $os->id],
|
||||
['name' => $os->name ?? $os->title ?? 'Materi ' . $os->id]
|
||||
);
|
||||
}
|
||||
|
||||
$oldQuestions = DB::table('lmsv2-old.questions')->get();
|
||||
|
||||
if ($oldQuestions->isEmpty()) {
|
||||
$this->warn('Tidak ada data soal ditemukan.');
|
||||
return;
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar(count($oldQuestions));
|
||||
$bar->start();
|
||||
|
||||
foreach ($oldQuestions as $q) {
|
||||
DB::table('question_banks')->updateOrInsert(
|
||||
['old_id' => $q->id],
|
||||
foreach ($oldQuestions as $old) {
|
||||
// Mapping Tipe
|
||||
$type = 'single';
|
||||
$oldType = strtolower($old->question_type ?? '');
|
||||
if (str_contains($oldType, 'multiple')) { $type = 'multiple'; }
|
||||
elseif (str_contains($oldType, 'true') || str_contains($oldType, 'boolean')) { $type = 'true_false'; }
|
||||
elseif (str_contains($oldType, 'desc') || str_contains($oldType, 'essay')) { $type = 'descriptive'; }
|
||||
|
||||
// Mapping Level
|
||||
$level = 'sedang';
|
||||
$oldLevel = strtolower($old->level ?? '');
|
||||
if (str_contains($oldLevel, 'mudah') || str_contains($oldLevel, 'easy')) { $level = 'mudah'; }
|
||||
elseif (str_contains($oldLevel, 'sulit') || str_contains($oldLevel, 'hard')) { $level = 'sulit'; }
|
||||
|
||||
// Mapping ID (Departemen, Posisi, Subject, Creator)
|
||||
$deptId = (!empty($old->section_id) && $old->section_id != 0) ? $old->section_id : null;
|
||||
$posId = (!empty($old->class_id) && $old->class_id != 0) ? $old->class_id : null;
|
||||
$subjId = (!empty($old->subject_id) && $old->subject_id != 0) ? $old->subject_id : null;
|
||||
$creatorId = (!empty($old->staff_id) && $old->staff_id != 0) ? $old->staff_id : null;
|
||||
|
||||
$correctAnswer = !empty($old->correct) ? [$old->correct] : null;
|
||||
|
||||
// Menggunakan Model agar array di-cast otomatis menjadi format yang benar
|
||||
\App\Models\QuestionBank::updateOrCreate(
|
||||
['old_id' => $old->id],
|
||||
[
|
||||
'question_text' => $q->question,
|
||||
'option_a' => $q->opt_a,
|
||||
'option_b' => $q->opt_b,
|
||||
'option_c' => $q->opt_c,
|
||||
'option_d' => $q->opt_d,
|
||||
'option_e' => $q->opt_e,
|
||||
'correct_answer' => $q->correct,
|
||||
'department_id' => $deptId,
|
||||
'position_id' => $posId,
|
||||
'subject_id' => $subjId,
|
||||
'question_type' => $type,
|
||||
'question_level' => $level,
|
||||
'question_text' => $old->question,
|
||||
'option_a' => $old->opt_a,
|
||||
'option_b' => $old->opt_b,
|
||||
'option_c' => $old->opt_c,
|
||||
'option_d' => $old->opt_d,
|
||||
'option_e' => $old->opt_e,
|
||||
'correct_answer' => $correctAnswer,
|
||||
'created_by' => $creatorId,
|
||||
'created_at' => $old->created_at ?? now(),
|
||||
'updated_at' => $old->updated_at ?? now(),
|
||||
]
|
||||
);
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
$this->info('Bank Soal berhasil ditarik!');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,17 +5,30 @@ namespace App\Console\Commands;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\QuestionBank;
|
||||
use App\Models\User;
|
||||
use App\Models\Subject;
|
||||
|
||||
class MigrateOldQuestions extends Command
|
||||
{
|
||||
// Ini adalah 'kunci' agar command muncul di php artisan list
|
||||
protected $signature = 'lms:migrate-questions';
|
||||
protected $description = 'Menyalin data soal dari lmsv2-old.questions ke lmsv2.question_banks secara presisi';
|
||||
protected $description = 'Menyalin data soal & subject dari lmsv2-old ke lmsv2 secara presisi';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Memulai Sinkronisasi Bank Soal dari Database Lama...');
|
||||
$this->info('Memulai Sinkronisasi Master Subject & Bank Soal...');
|
||||
|
||||
// --- 1. SINKRONISASI MASTER SUBJECT ---
|
||||
$this->info('Menarik data Subject...');
|
||||
$oldSubjects = DB::table('lmsv2-old.subjects')->get();
|
||||
foreach ($oldSubjects as $os) {
|
||||
Subject::updateOrCreate(
|
||||
['id' => $os->id],
|
||||
['name' => $os->name ?? $os->title ?? 'Materi ' . $os->id]
|
||||
);
|
||||
}
|
||||
|
||||
// --- 2. SINKRONISASI BANK SOAL ---
|
||||
$this->info('Menarik data Bank Soal...');
|
||||
$oldQuestions = DB::table('lmsv2-old.questions')->get();
|
||||
|
||||
if ($oldQuestions->isEmpty()) {
|
||||
@@ -30,53 +43,41 @@ class MigrateOldQuestions extends Command
|
||||
|
||||
foreach ($oldQuestions as $old) {
|
||||
|
||||
// 1. Pemetaan Tipe Soal
|
||||
// Pemetaan Tipe & Level
|
||||
$type = 'single';
|
||||
$oldType = strtolower($old->question_type ?? '');
|
||||
if (str_contains($oldType, 'multiple')) { $type = 'multiple'; }
|
||||
elseif (str_contains($oldType, 'true') || str_contains($oldType, 'boolean')) { $type = 'true_false'; }
|
||||
elseif (str_contains($oldType, 'desc') || str_contains($oldType, 'essay')) { $type = 'descriptive'; }
|
||||
|
||||
// 2. Pemetaan Level
|
||||
$level = 'sedang';
|
||||
$oldLevel = strtolower($old->level ?? '');
|
||||
if (str_contains($oldLevel, 'mudah') || str_contains($oldLevel, 'easy')) { $level = 'mudah'; }
|
||||
elseif (str_contains($oldLevel, 'sulit') || str_contains($oldLevel, 'hard')) { $level = 'sulit'; }
|
||||
|
||||
// 3. Pemetaan Pembuat (Creator)
|
||||
$creatorId = null;
|
||||
if (isset($old->staff_id)) {
|
||||
$oldStaff = DB::table('lmsv2-old.staff')->where('id', $old->staff_id)->first();
|
||||
if ($oldStaff && !empty($oldStaff->email)) {
|
||||
$user = User::where('email', $oldStaff->email)->first();
|
||||
if ($user) $creatorId = $user->id;
|
||||
}
|
||||
}
|
||||
// Pemetaan ID (Lama ke Baru)
|
||||
$deptId = (!empty($old->section_id) && $old->section_id != 0) ? $old->section_id : null;
|
||||
$posId = (!empty($old->class_id) && $old->class_id != 0) ? $old->class_id : null;
|
||||
$subjId = (!empty($old->subject_id) && $old->subject_id != 0) ? $old->subject_id : null;
|
||||
$creatorId = (!empty($old->staff_id) && $old->staff_id != 0) ? $old->staff_id : null;
|
||||
|
||||
$correctAnswer = !empty($old->correct) ? [$old->correct] : null;
|
||||
|
||||
// 4. Penanganan Kunci Jawaban (Karena di Model di-cast sebagai Array)
|
||||
$correctAnswer = null;
|
||||
if (!empty($old->correct)) {
|
||||
// Dibungkus ke dalam array agar tidak error saat Laravel mencoba json_encode
|
||||
$correctAnswer = [$old->correct];
|
||||
}
|
||||
|
||||
// 5. Simpan menggunakan format nama kolom yang BARU
|
||||
QuestionBank::updateOrCreate(
|
||||
['old_id' => $old->id], // Pencocokan menggunakan old_id
|
||||
['old_id' => $old->id],
|
||||
[
|
||||
'department_id' => $deptId,
|
||||
'position_id' => $posId,
|
||||
'subject_id' => $subjId,
|
||||
'question_type' => $type,
|
||||
'question_level' => $level,
|
||||
'question_text' => $old->question,
|
||||
|
||||
// Opsi A sampai E
|
||||
'option_a' => $old->opt_a,
|
||||
'option_b' => $old->opt_b,
|
||||
'option_c' => $old->opt_c,
|
||||
'option_d' => $old->opt_d,
|
||||
'option_e' => $old->opt_e,
|
||||
|
||||
'correct_answer' => $correctAnswer, // Sudah dalam bentuk array
|
||||
|
||||
'correct_answer' => $correctAnswer,
|
||||
'created_by' => $creatorId,
|
||||
'created_at' => $old->created_at ?? now(),
|
||||
'updated_at' => $old->updated_at ?? now(),
|
||||
@@ -89,6 +90,6 @@ class MigrateOldQuestions extends Command
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
$this->info("Sinkronisasi Selesai! Berhasil memindahkan {$successCount} Soal beserta kunci jawabannya.");
|
||||
$this->info("Sinkronisasi Selesai! Berhasil memperbarui {$successCount} Soal.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use App\Models\User;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class MigrateUsers extends Command
|
||||
{
|
||||
protected $signature = 'lms:migrate-users';
|
||||
protected $description = 'Menggabungkan 65 Staff & 810 Students menjadi tabel Users terpadu dengan Role dinamis.';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// 1. Pastikan Role Tersedia
|
||||
Role::firstOrCreate(['name' => 'trainer', 'guard_name' => 'web']);
|
||||
Role::firstOrCreate(['name' => 'trainee', 'guard_name' => 'web']);
|
||||
|
||||
$this->info('1. Memproses 65 Data Staff (sebagai Trainer)...');
|
||||
$staffs = DB::table('lmsv2-old.staff')->get();
|
||||
|
||||
foreach ($staffs as $staff) {
|
||||
$email = !empty($staff->email) ? trim($staff->email) : 'staff_' . $staff->id . '@noemail.com';
|
||||
|
||||
// Gunakan email sebagai kunci penyatuan
|
||||
$user = User::updateOrCreate(
|
||||
['email' => $email],
|
||||
[
|
||||
// Gunakan 'name' dan 'surname' sesuai tabel staff lama
|
||||
'first_name' => !empty($staff->name) ? trim($staff->name) : 'Staff ' . $staff->id,
|
||||
'last_name' => !empty($staff->surname) ? trim($staff->surname) : '',
|
||||
'password' => User::where('email', $email)->exists() ? User::where('email', $email)->value('password') : (!empty($staff->password) ? $staff->password : Hash::make('rahasia1234')),
|
||||
'is_active' => $staff->status ?? 1,
|
||||
]
|
||||
);
|
||||
$user->assignRole('trainer');
|
||||
}
|
||||
$this->info('Data Staff berhasil diproses!');
|
||||
|
||||
$this->info('2. Memproses 810 Data Students (sebagai Trainee)...');
|
||||
$students = DB::table('lmsv2-old.students')->get();
|
||||
$bar = $this->output->createProgressBar(count($students));
|
||||
$bar->start();
|
||||
|
||||
foreach ($students as $student) {
|
||||
$email = !empty($student->email) ? trim($student->email) : 'student_' . $student->id . '@noemail.com';
|
||||
|
||||
$user = User::updateOrCreate(
|
||||
['email' => $email],
|
||||
[
|
||||
// Gunakan 'firstname' dan 'lastname' TANPA garis bawah
|
||||
'first_name' => !empty($student->firstname) ? trim($student->firstname) : 'Student ' . $student->id,
|
||||
'last_name' => !empty($student->lastname) ? trim($student->lastname) : '',
|
||||
'old_id' => $student->id,
|
||||
'password' => User::where('email', $email)->exists() ? User::where('email', $email)->value('password') : (!empty($student->password) ? $student->password : Hash::make('rahasia1234')),
|
||||
'is_active' => $student->status ?? 1,
|
||||
]
|
||||
);
|
||||
|
||||
// Jika akun ini sebelumnya sudah jadi Staff (Trainer), ia akan mendapatkan Role Trainee JUGA! (Rangkap 2)
|
||||
$user->assignRole('trainee');
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
|
||||
$total = User::count();
|
||||
$this->info("Migrasi Selesai! Total unik akun di sistem saat ini: $total User.");
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,6 @@ class DashboardController extends Controller
|
||||
*/
|
||||
public function sopIndex(): View
|
||||
{
|
||||
return view('pages.admin.sops.index');
|
||||
return view('pages.admin.sop.index');
|
||||
}
|
||||
}
|
||||
@@ -6,32 +6,26 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Department;
|
||||
use App\Models\Position;
|
||||
use App\Models\TrainingMatrix;
|
||||
use App\Models\Subject; // PASTIKAN SUBJECT DI-IMPORT
|
||||
use App\Models\User;
|
||||
use App\Models\QuestionBank;
|
||||
|
||||
class ExamManagementController extends Controller
|
||||
{
|
||||
/**
|
||||
* Menampilkan halaman Bank Soal dengan Filter Komprehensif
|
||||
*/
|
||||
public function questionBank(Request $request)
|
||||
{
|
||||
// 1. Siapkan Data untuk Dropdown Filter
|
||||
$departments = Department::orderBy('name', 'asc')->get();
|
||||
$positions = Position::orderBy('name', 'asc')->get();
|
||||
// $matrices = TrainingMatrix::orderBy('title', 'asc')->get();
|
||||
$subjects = Subject::orderBy('name', 'asc')->get(); // Mengambil data Subject
|
||||
|
||||
// Ambil daftar user yang pernah membuat soal (untuk filter 'Pembuat')
|
||||
// Sesuaikan nama relasi/kolom jika berbeda
|
||||
$creators = User::whereHas('roles', function($q){
|
||||
$q->whereIn('name', ['admin', 'trainer']);
|
||||
})->orderBy('first_name', 'asc')->get();
|
||||
|
||||
// 2. Query Utama dengan Relasi
|
||||
$query = QuestionBank::with(['department', 'position', 'creator']);
|
||||
// Load relasi subject, department, position, dan creator
|
||||
$query = QuestionBank::with(['subject', 'department', 'position', 'creator']);
|
||||
|
||||
// 3. Logika Filter Dinamis
|
||||
// Logika Filter
|
||||
if ($request->filled('search')) {
|
||||
$query->where('question_text', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
@@ -41,28 +35,55 @@ class ExamManagementController extends Controller
|
||||
if ($request->filled('position_id')) {
|
||||
$query->where('position_id', $request->position_id);
|
||||
}
|
||||
// if ($request->filled('matrix_id')) {
|
||||
// $query->where('training_matrix_id', $request->matrix_id);
|
||||
// }
|
||||
if ($request->filled('subject_id')) { // Filter berdasarkan Subject
|
||||
$query->where('subject_id', $request->subject_id);
|
||||
}
|
||||
if ($request->filled('question_type')) {
|
||||
$query->where('type', $request->question_type);
|
||||
$query->where('question_type', $request->question_type);
|
||||
}
|
||||
if ($request->filled('question_level')) {
|
||||
$query->where('level', $request->question_level);
|
||||
$query->where('question_level', $request->question_level);
|
||||
}
|
||||
if ($request->filled('created_by')) {
|
||||
$query->where('created_by', $request->created_by);
|
||||
}
|
||||
|
||||
// 4. Eksekusi Query dengan Pagination
|
||||
$questions = $query->latest()->paginate(15)->withQueryString();
|
||||
$totalQuestions = $questions->total();
|
||||
|
||||
return view('pages.admin.exams.question-bank', compact(
|
||||
'questions', 'totalQuestions', 'departments', 'positions', 'creators'
|
||||
'questions', 'totalQuestions', 'departments', 'positions', 'subjects', 'creators'
|
||||
));
|
||||
}
|
||||
|
||||
// Fungsi untuk menampilkan halaman Tambah Soal
|
||||
public function create()
|
||||
{
|
||||
$departments = Department::orderBy('name', 'asc')->get();
|
||||
$positions = Position::orderBy('name', 'asc')->get();
|
||||
$subjects = Subject::orderBy('name', 'asc')->get(); // Kirim Subject ke form
|
||||
|
||||
return view('pages.admin.exams.create', compact('departments', 'positions', 'subjects'));
|
||||
}
|
||||
|
||||
// Fungsi untuk menampilkan halaman Edit Soal
|
||||
public function edit($id)
|
||||
{
|
||||
$question = QuestionBank::findOrFail($id);
|
||||
$departments = Department::orderBy('name', 'asc')->get();
|
||||
$positions = Position::orderBy('name', 'asc')->get();
|
||||
$subjects = Subject::orderBy('name', 'asc')->get(); // Kirim Subject ke form
|
||||
|
||||
return view('pages.admin.exams.edit', compact('question', 'departments', 'positions', 'subjects'));
|
||||
}
|
||||
|
||||
// Fungsi Show untuk Detail Soal
|
||||
public function show($id)
|
||||
{
|
||||
$question = QuestionBank::with(['subject', 'department', 'position', 'creator'])->findOrFail($id);
|
||||
return view('pages.admin.exams.show', compact('question'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Menghapus banyak soal sekaligus (Bulk Delete)
|
||||
*/
|
||||
@@ -86,6 +107,6 @@ class ExamManagementController extends Controller
|
||||
*/
|
||||
public function importView()
|
||||
{
|
||||
return view('pages.admin.exams.import-questions');
|
||||
return view('pages.admin.exams.questions.import');
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class QuestionBank extends Model
|
||||
protected $fillable = [
|
||||
// Atribut Soal dari desain Anda
|
||||
'old_id',
|
||||
'subject',
|
||||
'subject_id',
|
||||
'question_type',
|
||||
'question_level',
|
||||
'passing_grade',
|
||||
@@ -36,6 +36,11 @@ class QuestionBank extends Model
|
||||
'correct_answer' => 'array', // Otomatis ubah JSON di database jadi Array di PHP
|
||||
];
|
||||
|
||||
public function subject()
|
||||
{
|
||||
return $this->belongsTo(Subject::class, 'subject_id');
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| RELASI DATABASE (Diperlukan agar Filter Dropdown & Tabel berfungsi)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Subject extends Model
|
||||
{
|
||||
protected $table = 'subjects';
|
||||
|
||||
// Mengizinkan pengisian massal untuk kolom id dan name
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'name'
|
||||
];
|
||||
|
||||
// Relasi balik ke QuestionBank (Opsional, tapi baik untuk ke depannya)
|
||||
public function questions()
|
||||
{
|
||||
return $this->hasMany(QuestionBank::class, 'subject_id');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user