This commit is contained in:
jouel88 2026-03-19 21:58:48 +07:00
parent cc98e0761e
commit e5cb2e916c
8 changed files with 192 additions and 113 deletions

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# MPG HRIS - Web Management System
Sistem Manajemen Sumber Daya Manusia (HRIS) terintegrasi untuk pengelolaan data pegawai, presensi, lembur, dan perizinan. Dibuat menggunakan framework Laravel dengan fokus pada performa dan kemudahan penggunaan.
## 🚀 Tech Stack
- **Backend**: Laravel 12.x (PHP 8.2+)
- **Frontend**: Blade Templates + Tailwind CSS (v3.x)
- **Database**: MySQL 8.x
- **Icons**: Heroicons (SVG inline)
- **Komponen UI**: Custom Blade Components (Table, Modal, Input, Button, dll.)
## 🛠 Panduan Instalasi
Ikuti langkah-langkah berikut untuk menjalankan proyek di lingkungan lokal:
1. **Clone & Setup Environment**
```bash
cp .env.example .env
# Sesuaikan DB_DATABASE, DB_USERNAME, DB_PASSWORD di .env
```
2. **Instal Dependensi Backend**
```bash
composer install
```
3. **Instal Dependensi Frontend**
```bash
npm install
npm run build # atau npm run dev untuk development
```
4. **Generate App Key & Database**
```bash
php artisan key:generate
php artisan migrate --seed
```
5. **Akses Aplikasi**
Jalankan `php artisan serve` dan buka `http://localhost:8000`.
## 📂 Struktur Proyek & Arsitektur
Proyek ini mengikuti standar **Clean Code** Laravel dengan pemisahan tanggung jawab yang jelas:
- **Form Requests**: Semua validasi input diletakkan di `app/Http/Requests`.
- **Controllers**: Fokus pada pengaturan trafik. Logika query berat dipisahkan ke Eloquent Scopes atau Service.
- **Models**: Menggunakan relasi Eloquent yang jelas dan penamaan kolom `snake_case` Bahasa Indonesia.
- **Resources**: UI dibangun menggunakan komponen Blade di `resources/views/components`.
## 📜 Aturan Pengembangan (Project Rules)
1. **Bahasa**: Gunakan Bahasa Indonesia untuk penjelasan, komentar, dan dokumentasi.
2. **Naming Convention**:
- Primary Key: `id_nama_tabel` (contoh: `id_divisi`, `id_jabatan`).
- Tabel Master: `snake_case`.
3. **UI Components**: Gunakan komponen yang sudah ada untuk konsistensi:
- `<x-table>`: Wrapper tabel standar.
- `<x-input>` & `<x-select>`: Input form standar.
- `<x-button>` & `<x-back-button>`: Tombol aksi.
- `<x-modal>`: Untuk CRUD Data Master sederhana.
## 🔑 Akun Default (Seeder)
- **Super Admin**: `superadmin@mpg.co.id` / `password`
- **Admin**: `admin@mpg.co.id` / `admin123`
- **Pegawai**: `budi@mpg.co.id` / `Mpg123!`
---
Developed by MPG Team.

View File

@ -10,55 +10,38 @@ class DatabaseSeeder extends Seeder
{
public function run(): void
{
DB::table('lokasi_kantor')->insert([
[
'nama_kantor' => 'Notis Digital',
'alamat' => 'Jln. Kalimantan No. 1, Jember',
'latitude' => -8.172110,
'longitude' => 113.700550,
'radius_meter' => 200,
'created_at' => now(),
'updated_at' => now(),
],
[
'nama_kantor' => 'Curah Manis Studio',
'alamat' => 'Jln. Mastrip, Jember',
'latitude' => -8.172110,
'longitude' => 113.700550,
'radius_meter' => 200,
'created_at' => now(),
'updated_at' => now(),
]
]);
User::factory(10)->create([
'id_kantor' => 1,
]);
User::factory(15)->create([
'id_kantor' => 2,
]);
User::factory()->create([
'nama_lengkap' => 'Julian (Super Admin)',
'email' => 'julian@admin.com',
'password' => bcrypt('password'),
'divisi' => 'IT',
'jabatan' => 'Manager',
'id_kantor' => 1,
]);
// Panggil MasterDataSeeder terlebih dahulu untuk data dasar (Divisi, Jabatan, Kantor, Role, User Admin)
$this->call([
MasterDataSeeder::class,
]);
// Tambahan data demo jika diperlukan (Opsional)
$kantor = \App\Models\Kantor::first();
$divisi = \App\Models\Divisi::first();
$jabatan = \App\Models\Jabatan::where('nama_jabatan', 'Staff')->first() ?? \App\Models\Jabatan::first();
$users = User::factory(5)->create([
'id_kantor' => $kantor->id_kantor,
'id_divisi' => $divisi->id_divisi,
'id_jabatan' => $jabatan->id_jabatan,
]);
$rolePegawai = \App\Models\Role::where('nama_role', 'pegawai')->first();
if ($rolePegawai) {
foreach ($users as $user) {
$user->roles()->attach($rolePegawai->id_role);
}
}
// Panggil seeder fitur lainnya
$this->call([
PermissionSeeder::class,
PresensiStatusSeeder::class,
PresensiSeeder::class,
JenisIzinSeeder::class,
StatusPengajuanSeeder::class,
PengajuanIzinSeeder::class,
SuperAdminSeeder::class, // Menambah super administrator dengan role admin
]);
}
}

View File

@ -9,13 +9,12 @@ class JenisIzinSeeder extends Seeder
{
public function run(): void
{
DB::table('jenis_izin')->delete();
DB::table('jenis_izin')->insert([
['id_jenis_izin' => 1, 'nama_izin' => 'Sakit'],
['id_jenis_izin' => 2, 'nama_izin' => 'Cuti'],
['id_jenis_izin' => 3, 'nama_izin' => 'Izin'],
]);
$types = ['Sakit', 'Cuti', 'Izin'];
foreach ($types as $type) {
DB::table('jenis_izin')->updateOrInsert(
['nama_izin' => $type],
['updated_at' => now(), 'created_at' => now()]
);
}
}
}

View File

@ -13,54 +13,74 @@ class MasterDataSeeder extends Seeder
{
public function run()
{
$divIT = Divisi::firstOrCreate(['nama_divisi' => 'Information Technology']);
$divHR = Divisi::firstOrCreate(['nama_divisi' => 'Human Resources']);
Divisi::firstOrCreate(['nama_divisi' => 'Finance & Accounting']);
Divisi::firstOrCreate(['nama_divisi' => 'Marketing']);
$divIT = Divisi::create(['nama_divisi' => 'Information Technology']);
$divHR = Divisi::create(['nama_divisi' => 'Human Resources']);
Divisi::create(['nama_divisi' => 'Finance & Accounting']);
Divisi::create(['nama_divisi' => 'Marketing']);
$jabMgr = Jabatan::firstOrCreate(['nama_jabatan' => 'Manager']);
$jabSpv = Jabatan::firstOrCreate(['nama_jabatan' => 'Supervisor']);
$jabStf = Jabatan::firstOrCreate(['nama_jabatan' => 'Staff']);
Jabatan::firstOrCreate(['nama_jabatan' => 'Internship']);
$jabMgr = Jabatan::create(['nama_jabatan' => 'Manager']);
$jabSpv = Jabatan::create(['nama_jabatan' => 'Supervisor']);
$jabStf = Jabatan::create(['nama_jabatan' => 'Staff']);
Jabatan::create(['nama_jabatan' => 'Internship']);
$kPusat = Kantor::firstOrCreate(
['nama_kantor' => 'Kantor Pusat'],
[
'alamat' => 'Jl. Jendral Sudirman No. 1, Jakarta',
'latitude' => -6.2088,
'longitude' => 106.8456,
'radius' => 200
]
);
$kPusat = Kantor::create([
'nama_kantor' => 'Kantor Pusat',
'alamat' => 'Jl. Jendral Sudirman No. 1, Jakarta',
'tipe' => 'Pusat'
]);
$kCabang = Kantor::firstOrCreate(
['nama_kantor' => 'Curah Manis Studio'],
[
'alamat' => 'Jl. Curah Manis, Malang',
'latitude' => -7.9839,
'longitude' => 112.6214,
'radius' => 200
]
);
$kCabang = Kantor::create([
'nama_kantor' => 'Curah Manis Studio',
'alamat' => 'Jl. Curah Manis, Malang',
'tipe' => 'Cabang'
]);
// Roles
$roleAdmin = \App\Models\Role::firstOrCreate(['nama_role' => 'admin']);
$roleHrd = \App\Models\Role::firstOrCreate(['nama_role' => 'hrd']);
$roleManager = \App\Models\Role::firstOrCreate(['nama_role' => 'manager']);
$rolePegawai = \App\Models\Role::firstOrCreate(['nama_role' => 'pegawai']);
User::create([
'nama_lengkap' => 'Super Administrator',
'email' => 'admin@mpg.co.id',
'password' => Hash::make('admin123'),
'role' => 'admin',
'nik' => '999999999',
'status_aktif' => 1,
$admin = User::updateOrCreate(
['email' => 'admin@mpg.co.id'],
[
'nama_lengkap' => 'Super Administrator',
'password' => Hash::make('admin123'),
'nik' => '999999999',
'status_aktif' => 1,
'id_divisi' => $divIT->id_divisi,
'id_jabatan' => $jabMgr->id_jabatan,
'id_kantor' => $kPusat->id_kantor,
'tgl_bergabung' => now(),
]
);
if (!$admin->roles()->where('nama_role', 'admin')->exists()) {
$admin->roles()->attach($roleAdmin->id_role);
}
'id_divisi' => $divIT->id_divisi,
'id_jabatan' => $jabMgr->id_jabatan,
'id_kantor' => $kPusat->id_kantor,
'tgl_bergabung' => now(),
]);
User::create([
'nama_lengkap' => 'Budi Santoso',
'email' => 'budi@mpg.co.id',
'password' => Hash::make('Mpg123!'),
'role' => 'pegawai',
'nik' => '350712345678',
'status_aktif' => 1,
'id_divisi' => $divHR->id_divisi,
'id_jabatan' => $jabStf->id_jabatan,
'id_kantor' => $kCabang->id_kantor,
'tgl_bergabung' => now(),
]);
$pegawai = User::updateOrCreate(
['email' => 'budi@mpg.co.id'],
[
'nama_lengkap' => 'Budi Santoso',
'password' => Hash::make('Mpg123!'),
'nik' => '350712345678',
'status_aktif' => 1,
'id_divisi' => $divHR->id_divisi,
'id_jabatan' => $jabStf->id_jabatan,
'id_kantor' => $kCabang->id_kantor,
'tgl_bergabung' => now(),
]
);
if (!$pegawai->roles()->where('nama_role', 'pegawai')->exists()) {
$pegawai->roles()->attach($rolePegawai->id_role);
}
}
}

View File

@ -21,10 +21,8 @@ public function run(): void
foreach ($users as $user) {
try {
$idJenis = rand(1, 3);
$idStatus = rand(1, 3);
$idJenis = DB::table('jenis_izin')->inRandomOrder()->first()->id_jenis_izin;
$idStatus = DB::table('status_pengajuan')->inRandomOrder()->first()->id_status;
$tglMulai = now()->addDays(rand(-10, 10));
$tglSelesai = (clone $tglMulai)->addDays(rand(1, 3));

View File

@ -40,12 +40,15 @@ public function run(): void
$randomMinutes = rand(-10, 30);
$jamMasuk = $shiftStart->copy()->addMinutes($randomMinutes);
$status = 1;
$idTepat = DB::table('status_presensi')->where('nama_status', 'Tepat Waktu')->value('id_status') ?? 1;
$idTelat = DB::table('status_presensi')->where('nama_status', 'Terlambat')->value('id_status') ?? 2;
$status = $idTepat;
$alasan = null;
$toleransi = 10;
if ($randomMinutes > $toleransi) {
$status = 2;
$status = $idTelat;
$alasan = 'Terlambat ' . ($randomMinutes) . ' menit (Dummy Data)';
}

View File

@ -10,14 +10,20 @@ class PresensiStatusSeeder extends Seeder
public function run(): void
{
$statuses = [
'Tepat Waktu',
'Terlambat',
'Izin',
'Sakit',
'Alpha',
];
DB::table('status_presensi')->insertOrIgnore([
['id_status' => 1, 'nama_status' => 'Tepat Waktu', 'created_at' => now(), 'updated_at' => now()],
['id_status' => 2, 'nama_status' => 'Terlambat', 'created_at' => now(), 'updated_at' => now()],
['id_status' => 3, 'nama_status' => 'Izin', 'created_at' => now(), 'updated_at' => now()],
['id_status' => 4, 'nama_status' => 'Sakit', 'created_at' => now(), 'updated_at' => now()],
['id_status' => 5, 'nama_status' => 'Alpha', 'created_at' => now(), 'updated_at' => now()],
]);
foreach ($statuses as $status) {
DB::table('status_presensi')->updateOrInsert(
['nama_status' => $status],
['created_at' => now(), 'updated_at' => now()]
);
}
$this->command->info('Tabel status_presensi berhasil diisi!');
}

View File

@ -9,13 +9,12 @@ class StatusPengajuanSeeder extends Seeder
{
public function run(): void
{
DB::table('status_pengajuan')->delete();
DB::table('status_pengajuan')->insert([
['id_status' => 1, 'nama_status' => 'Disetujui'],
['id_status' => 2, 'nama_status' => 'Pending'],
['id_status' => 3, 'nama_status' => 'Ditolak'],
]);
$statuses = ['Disetujui', 'Pending', 'Ditolak'];
foreach ($statuses as $status) {
DB::table('status_pengajuan')->updateOrInsert(
['nama_status' => $status],
['updated_at' => now(), 'created_at' => now()]
);
}
}
}