# Migrasi Data Staff & Tenaga Kerja dari Excel

**File sumber:** `docs/DAFTAR NAMA  STAFF DAN TENAGA.xlsx` (2 sheet)
**Target:** Tabel `employees` + relasi (`positions`, `shifts`, `employee_projects`)
**Mode:** INSERT / UPDATE / HAPUS (sinkron penuh berdasarkan NIK)

---

## 1. Ringkasan

| Sheet | Baris data | NIK valid | Keterangan |
|---|---|---|---|
| `Data Base STaff` | 129 | 129 | Semua `Aktif`; Job Level: Staff / Supervisor / Head |
| `Data Base Tenaga Harian` | 129 | **53** | 76 baris **tanpa NIK → di-SKIP** (data tidak lengkap) |
| **Total valid** | — | **182** | 129 staff + 53 tenaga harian |

> 76 baris tenaga harian tanpa NIK tidak bisa di-sinkron (tidak ada kunci) — dilaporkan sebagai `skipped_no_nik` dan TIDAK diproses.

---

## 2. Kolom Baru di Aplikasi (migration)

Field Excel yang belum ada di tabel `employees` → **ditambahkan** (nullable, tidak mengganggu data lama):

| Kolom DB | Tipe | Sumber Excel |
|---|---|---|
| `department` | string(50) nullable | Departmen (Pusat/Cikarang/Project) |
| `employee_status` | string(20) nullable | Status Employee (Permanent/Contract/Probation/Daily Worker) |
| `ptkp_status` | string(10) nullable | PTKP Status (TK/0, K/0, K/1, K/2, K/3) |
| `bank_name` | string(50) nullable | Bank Name (BCA) |
| `bank_account` | string(30) nullable | Bank Account (nomor rekening) |
| `bank_account_name` | string(100) nullable | Nama Bank (pemilik rekening) |
| `religion` | string(20) nullable | Agama (Islam) |
| `approve_by` | string(100) nullable | Acc Aproval (nama atasan) |
| `is_active_absen` | boolean default 1 | Aktipkan Status presensi absen (ya/tidak) |
| `work_time_type` | string(20) nullable | tipe jam kerja |
| `work_time_in` | time nullable | jam masuk |
| `work_time_out` | time nullable | jam keluar |

---

## 3. Mapping Field Excel → Database

| Kolom Excel | Kolom DB | Catatan |
|---|---|---|
| No karyawan | `nik` | **Kunci utama** (normalisasi: uppercase + trim) |
| Nama Karyawan | `name` | — |
| Departmen | `department` | kolom baru |
| Jabatan | `position_id` | lookup `positions.name` (case-insensitive) → buat baru jika belum ada |
| Pendidikan | `education_level` | — |
| Job Level | `staff_status_id` | Staff/Supervisor/Head → **1 (Staff)**; Man Power → **2 (Tenaga Kerja)** |
| Mulai Bekerja | `join_date` | validasi tanggal; catatan: ada nilai `1905-07-18` (data tidak valid) |
| Status Keaktifan | `is_active` | Aktif → 1; lainnya → 0 |
| Status Employee | `employee_status` | kolom baru |
| Email | `email` | — |
| Tanggal Lahir | `birth_date` | — |
| Tempat Lahir | `birth_place` | — |
| Alamat | `address` | — |
| NPWP | `npwp` | — |
| PTKP Status | `ptkp_status` | kolom baru |
| Bank Name | `bank_name` | kolom baru |
| Bank Account | `bank_account` | kolom baru |
| Nama Bank | `bank_account_name` | kolom baru |
| nik (KTP) | `identity_no` | — |
| Mobile Phone | `phone` | — |
| Agama | `religion` | kolom baru |
| Jenis Kelamin | `sex` | Male/Laki-Laki → 1; Female/Perempuan → 2 |
| Acc Aproval | `approve_by` | kolom baru |
| Aktipkan presensi | `is_active_absen` | kolom baru |
| tipe jam kerja / jam masuk / jam keluar | `work_time_*` | kolom baru |
| Proyek | `employee_projects.project_id` | lookup `projects.name` (case-insensitive) |
| Lembur Efektif | `is_bor_efektif` | Ya → 1; No → 0 |
| Upah Tenaga Kerja | `basic_salary` | sheet Tenaga Harian |
| Uang Makan Staff | `meal_allowance` | sheet Staff (nilai pokok) |
| Tunj Luar Kota | `tunj_luar_kota` | — |
| Tunj Malam | `tunj_malam` | — |
| Tunj Lembur | `tunj_lembur` | — |
| Pot Telat | `pot_telat` | — |

> **Keputusan penting:** kolom **Upah Tenaga Kerja** (sheet TK) → `basic_salary`; **Uang Makan Staff** (sheet Staff) → `meal_allowance`. Keduanya tidak saling timpa karena sheet berbeda.

---

## 4. Algoritma Sinkronisasi

Kunci: **NIK** (`nik`, di-normalisasi UPPERCASE/trim).

```
Untuk SETIAP baris valid (NIK ada):
  1. Cari employees.nik == NIK
  2. Ada?  → UPDATE: set semua field dari mapping (kecuali nilai kosong/None)
  3. Tidak ada? → INSERT: employees baru (staff_status sesuai sheet) + relasi
  4. Setelah proses semua baris:
     DELETE: employees (aktif) yang NIK-nya TIDAK ada di daftar Excel
```

### 4a. UPDATE (NIK sudah ada)
- Hanya menimpa kolom yang **terpetakan dari Excel**.
- Kolom Excel **kosong/None** → **tidak menimpa** nilai existing (data aplikasi dipertahankan).
- Field aplikasi yang tidak ada di Excel (blood_type, marital_status, city, shift_id, dsb.) → **tetap** (tidak di-reset).

### 4b. INSERT (NIK belum ada)
- `staff_status_id` dari Job Level (Staff→1, Man Power→2).
- `position_id` lookup nama → auto-create posisi baru jika belum ada.
- `shift_id` → **tidak di-set** (default; jam kerja disimpan di `work_time_in/out`; shift manual via aplikasi).
- `project_id` → `employee_projects` (lookup nama proyek, case-insensitive, pilih proyek aktif pertama; buat baru jika belum ada).
- `city_id` → null (Excel tidak memuat kota) — bisa dilengkapi manual.
- `created_by` = user migrasi (1 = admin).

### 4c. DELETE / NONAKTIFKAN (tidak ada di Excel)
- Karyawan aktif (`is_active=1`) yang NIK-nya tidak muncul di Excel → **dinonaktifkan** (`is_active=0`) + `employee_status='Nonaktif (Migrasi)'` bila kolom baru ada.
- **Tidak menghapus fisik** (hindari FK `attendances`, `payrolls` yang RESTRICT). Opsi hapus fisik tersedia dengan flag `--hard-delete` hanya untuk karyawan **tanpa data transaksi** (attendance/payroll/kas).

---

## 5. Penanganan Data Bermasalah

| Masalah | Penanganan |
|---|---|
| 76 baris tenaga tanpa NIK | SKIP → dicatat di laporan `skipped_no_nik` |
| Tanggal `1905-07-18` | tetap di-import sebagai data mentah + peringatan di laporan `date_warning` |
| Nama proyek duplikat ("Alam Sutera" vs "KOMERSIAL ALAM SUTERA") | lookup case-insensitive; pilih proyek **aktif pertama** (id terkecil); relasi disimpan ke proyek yang ditemukan |
| NIK duplikat dalam Excel | hanya baris pertama yang diproses, sisanya `skipped_duplicate_nik` |
| Jabatan baru (tidak ada di `positions`) | otomatis dibuat (`position_id` baru) |
| Jenis Kelamin kosong | tidak menimpa (skip) |
| Upah kosong (None) | tidak menimpa `basic_salary` existing |

---

## 6. Perintah

```bash
# 1) Tambah kolom baru
php artisan migrate --force

# 2) Dry-run (laporan saja, tidak mengubah data)
php artisan lpk:import-staff-xlsx --path="docs/DAFTAR NAMA  STAFF DAN TENAGA.xlsx" --dry-run

# 3) Eksekusi
php artisan lpk:import-staff-xlsx --path="docs/DAFTAR NAMA  STAFF DAN TENAGA.xlsx"

# 4) (Opsional) hapus fisik karyawan tanpa transaksi yang tidak ada di Excel
php artisan lpk:import-staff-xlsx --path="..." --hard-delete
```

## 7. Output Laporan

Command mencetak ringkasan (dan menyimpan ke storage/logs):

```
Sheet: Data Base STaff  → insert=12, update=117, skipped_no_nik=0, skipped_duplicate=0
Sheet: Data Base Tenaga Harian → insert=40, update=13, skipped_no_nik=76
TOTAL: inserted=52, updated=130
DEACTIVATED (tidak ada di Excel): 10
date_warning: 3 (contoh: 1905-07-18)
```

## 8. Uji & Rollback

1. **Backup sebelum migrasi**:
   ```
   mysqldump ... employees employee_projects positions > backup-employees-YYYYMMDD.sql
   ```
2. **Uji di DB lokal dulu** (dry-run → cek laporan → eksekusi → cek halaman Relasi → Karyawan).
3. **Rollback**: restore backup SQL (data employees kembali sebelum migrasi).

## 9. Risiko

- Penonaktifan massal karyawan yang tidak ada di Excel (cek laporan `DEACTIVATED` dulu).
- Data lama yang lebih lengkap bisa tertimpa oleh nilai Excel yang lebih pendek — mitigasi: kolom kosong tidak menimpa.
- Proyek duplikat menyebabkan relasi proyek tidak konsisten — normalisasi nama proyek disarankan sebelum migrasi.
- Absensi/payroll lama tetap mereferensikan NIK lama — tidak terpengaruh.

## 10. Checklist Sebelum Eksekusi

- [ ] Backup tabel `employees`, `employee_projects`, `positions`
- [ ] Jalankan `php artisan migrate` (kolom baru)
- [ ] Dry-run & review laporan
- [ ] Review daftar `DEACTIVATED` (karyawan yang akan dinonaktifkan)
- [ ] Eksekusi di DB lokal → verifikasi halaman Relasi → Karyawan
- [ ] Eksekusi di produksi → verifikasi
