# Development Jurnal - Everest Project

## 15 Juli 2026 - Perbaikan Kalkulasi Penggunaan Material (Qty Digunakan) pada SPK

### 🔴 Permasalahan
Terdapat ketidaksesuaian/kesalahan pada jumlah "digunakan" (Qty Used/Kredit) di laporan penggunaan material dan pada pop-up `selectItem` (pemilihan produk paket). Misalnya, pada item "BRACKET HODA", sistem menampilkan angka 51 unit, padahal seharusnya sudah ditugaskan sebanyak 81 unit (dari total 141 B.O.M).

### 🔍 Akar Masalah
Sistem sebelumnya menghitung nilai penggunaan (`total_digunakan` / `qty_kredit`) dengan membaca tabel `project_sub_tasklist_komposisi`. Padahal, tabel ini baru diisi/ditulis **saat SPK diproses atau diselesaikan** oleh pelaksana (Progress QC). Sedangkan penugasan aktual (saat SPK diterbitkan/disimpan) tercatat di tabel `project_komposisi_sub_workoder`. Akibatnya, barang yang sudah ditugaskan namun SPK-nya belum selesai dikerjakan tidak terhitung sebagai barang yang "digunakan".

### 🛠️ Solusi & Perbaikan
Mengubah sumber data perhitungan penggunaan material dari tabel progress (`project_sub_tasklist_komposisi`) ke tabel penugasan awal (`project_komposisi_sub_workoder` dengan filter `jenis_transaksi = 'sub_wo'`). Data return tetap diambil dari tabel progress.

### 📄 File yang Di-upgrade (Modified)

1. **`application/modules/master_project/models/Mdls/MdlProdukProject.php`**
   - Mengubah fungsi `getRekapKomposisi()` agar menghitung `total_digunakan` berdasarkan tabel `project_komposisi_sub_workoder` (saat SPK dibuat).
   - Mengambil data `total_return` melalui pre-aggregate subquery dari `project_sub_tasklist_komposisi` untuk mencegah duplikasi Cartesian.
   - Menghapus param `$jenisTransaksi` karena filternya sudah langsung dibenamkan dalam query.

2. **`application/modules/master_project/controllers/MasterData.php`**
   - Menyesuaikan *caller* `getRekapKomposisi($pid)` pada dua tempat (baris 787 & 9288) dengan membuang parameter string "sub_wo" yang kini usang.
   - Mengubah logika dalam fungsi `selectItem()` (khususnya *case* `"produk_paket"`):
     - Mengubah referensi pemanggilan `MdlProjectSubTasklistKomposisi` menjadi `MdlProjectKomposisiWorkorderSub` agar sejalan dengan fungsi `getRekapKomposisi()`.
     - Menulis query manual `SELECT ... SUM(jml_return)` terpisah dari `project_sub_tasklist_komposisi` untuk mendapatkan nilai *return* saja.
     - Melakukan sinkronisasi & *merge* array PHP (`$returnDataMap`) ke dalam rekap data akhir (`$arrSubTasklistKomp`) agar nilai `jml_return` tetap akurat dan struktur JSON yang dilempar ke AJAX/Javascript tidak pecah.

---

## 15 Juli 2026 - Pencegahan Dobel Save (Double Entry) pada Penugasan SPK & Tambahan Biaya

### 🔴 Permasalahan
Saat user mengklik tombol "Simpan" berkali-kali secara cepat (double click) pada halaman penugasan SPK (`simpanTasklist`) dan Pengajuan Tambahan Biaya (`simpanTasklistTambahan`), sistem memproses simpan ganda. Hal ini mengakibatkan data SPK dan material pendukungnya masuk secara duplikat.

### 🔍 Akar Masalah
1. **Backend**: Tidak adanya mekanisme *lock* (penguncian transaksi) atau pengecekan *race condition* sebelum sistem memproses penyimpanan insert ke tabel penugasan. Akibatnya, *request* HTTP paralel dari user diproses berbarengan oleh database.
2. **Frontend**: Meskipun fitur loading/spinner sudah ada, jeda asinkron yang sangat kecil (latency) atau tombol yang belum sepenuhnya disable memberi celah klik ganda. Pada fitur Tambahan Biaya, tombol konfirmasi murni belum di-disable saat data dikirim.

### 🛠️ Solusi & Perbaikan
1. **Backend (Row Locking & Validation)**: Menambahkan protokol penguncian baris (`FOR UPDATE`) di awal transaksi database, dipadukan dengan validasi interval waktu (Pengecekan record identik dalam 10 detik terakhir).
2. **Frontend (Disabling DOM)**: Menguatkan disable properties pada tombol trigger `submit` dan `load` AJAX setelah interaksi pertama pengguna untuk mematikan peluang klik lanjutan.

---

## 15 Juli 2026 - Perbaikan Bug Material Menggantung dan Race Condition pada Pembatalan SPK

### 🔴 Permasalahan
Saat user membatalkan SPK (Tasklist), material yang dialokasikan untuk SPK tersebut tidak dikembalikan (masih berstatus "Digunakan" pada pop-up `selectItem`). Selain itu, jika tombol Batal diklik dua kali dengan cepat (double-click), transaksi pembatalan berjalan ganda.

### 🔍 Akar Masalah
1. **Perubahan Source of Truth (Material Bug):** Pada update sebelumnya, kita memindahkan rujukan material "Digunakan" ke tabel penugasan `project_komposisi_sub_workoder`. Namun, fungsi `pembatalanTasklist()` tidak menghapus (trash) data penugasan `sub_wo` tersebut saat membatalkan SPK (ia hanya men-trash header `project_tasklist`). Akibatnya, `getRekapKomposisi()` tetap menghitung penugasan dari SPK yang sudah dibatalkan.
2. **Missing Lock (Race Condition):** Blok kode `$this->db->trans_start()` pada fungsi tersebut tidak disertai penguncian tingkat baris (Row Locking) sehingga tidak kebal terhadap parallel HTTP request.

### 🛠️ Solusi & Perbaikan
1. **Row Locking:** Menyisipkan `SELECT ... FOR UPDATE` untuk `project_produk` tepat setelah transaksi dimulai, agar *request* pembatalan yang serentak harus antre.
2. **Sinkronisasi Trash Material:** Menambahkan logika eksekusi *update* untuk melakukan *trash* (`status=0, trash=1`) terhadap record di tabel `project_komposisi_sub_workoder` (`jenis_transaksi='sub_wo'`) ketika sebuah SPK dibatalkan, sesuai dengan arsitektur data material yang baru.

### 📄 File yang Di-upgrade (Modified)

1. **`application/modules/master_project/controllers/MasterData.php`**
   - Memodifikasi fungsi `pembatalanTasklist()`.
   - Baris 22838: Menambahkan `$lockCheck = $this->db->query("SELECT id FROM project_produk WHERE id = ? FOR UPDATE", array($project_id))->row();`.
   - Baris 22913: Menambahkan `$os->updateData()` untuk me-*reject/trash* `$subwo_id` agar pengecekan di `getRekapKomposisi()` menjadi bersih dan tidak meninggalkan *ghost allocation*.

2. **`application/modules/master_project/models/Mdls/MdlProdukProject.php`**
   - Memperbaiki formula matematika pada perhitungan *Sisa Qty* (`sisa` dan `sisa_rp`) di `getRekapKomposisi()`. Sebelumnya *Retur* malah mengurangi sisa, seharusnya *Retur* **ditambahkan** ke sisa. (`MAX(k.jml) - IFNULL(SUM(p.jml), 0) + IFNULL(MAX(ret.sum_return), 0)`)

3. **`application/modules/master_project/views/data.php`**
   - Mengubah logika perhitungan persentase (*Progress Bar*) material menjadi `(Total Alokasi / Komposisi) * 100`. 
   - Menambahkan pembulatan satu desimal (`round($persen, 1)`) agar persentase tidak memanjang ke samping (misal: *100%* dari yang sebelumnya *120.8547008547%*).

4. **Koreksi Logika Bisnis Sisa Qty (Return ke Pusat):**
   - Merujuk pada asas operasional, material yang di-*return* akan dikembalikan langsung ke pusat, sehingga tidak menambah sisa budget yang bisa ditarik lagi oleh proyek. Oleh karena itu, perhitungan Sisa Qty di `MdlProdukProject.php` diubah murni menjadi `Sisa = Komposisi - Digunakan` tanpa melibatkan parameter `Return`. 
   - Konsep ini menjadikan representasi data akurat: 100% Selesai artinya seluruh Budget sudah habis ditarik (Sisa = 0), meskipun ada beberapa yang diretur/dikembalikan.

### 📄 File yang Di-upgrade (Modified) (Bagian Double Entry)

1. **`application/modules/master_project/controllers/MasterData.php`**
   - **Backend (`simpanTasklist` & `simpanTasklistTambahan`)**: 
     - Menambahkan آلية **Row Locking (`FOR UPDATE`)** dengan melakukan query *lock* pada parent/master project (`project_produk`) tepat setelah transaksi dimulai (`$this->db->trans_start()`).
     - Menambahkan **Validasi Duplikasi Waktu (10 Detik)** dengan mendeteksi SPK/Tugas baru yang identik (fase, sub-fase, unit, paket) dalam rentang waktu <= 10 detik. Jika ditemukan, request otomatis di-rollback (dibatalkan).
   - **Frontend (`.btn_kirim_request`)**:
     - Memodifikasi skrip *click handler* (pada bagian `simpanTasklistTambahan`) agar melakukan manipulasi `$(this).prop('disabled', true)` tepat setelah modal *SweetAlert* dikonfirmasi "Ya". Hal ini mencegah pengguna meresubmit form tambahan biaya secara membabi buta.

---

## 15 Juli 2026 - Normalisasi Data Progres Legacy yang Melebihi 100%

### 🔴 Permasalahan
Banyak *project legacy* (contohnya Project 140, 145, 146) yang menampilkan persentase progres (`persen_progress`) dan nilai harga progres (`harga_progress`) yang tidak valid, bahkan mencapai angka 300%.

### 🔍 Akar Masalah
Terjadi akumulasi (penumpukan) angka progres akibat bug masa lalu. Sebelumnya, saat dilakukan "Pembatalan SPK / Tasklist" menggunakan `pembatalanTasklist()`, sistem lupa/tidak mengurangi total `harga_progress` dan `persen_progress` pada master tabel `project_produk`. Hal ini menyebabkan angka progres seolah-olah terus bertambah namun tidak pernah surut kendati SPK tersebut berulang kali dibatalkan.

### 🛠️ Solusi & Perbaikan
Fungsi `normalisasiProgressProject()` di controller `MasterData.php` sebetulnya sudah dirancang untuk memeriksa kesenjangan ini dengan jalan menghitung saldo secara riil melalui `SUM` dari tabel *detail progress* (`project_sub_tasklist_komposisi`). Namun, fungsi tersebut tidak berjalan karena dikunci (hardcode ID 92 dan memiliki skrip penghentian `die()`). Setelah fungsi ini dimodifikasi, kita mengeksekusinya untuk menormalkan seluruh *project legacy*.

### 📄 File yang Di-upgrade (Modified)

1. **`application/modules/master_project/controllers/MasterData.php`**
   - Mengubah fungsi `normalisasiProgressProject()`:
     - Menghapus baris `$produk_id = 92;` agar dapat menangani parameter *dynamic* / *all projects* secara luas.
     - Menghapus perintah `die("MATI DULU BELUM COMMIT");` untuk mengizinkan database mengeksekusi `$this->db->trans_commit()` dan memperbarui record secara sesungguhnya.

2. **Eksekusi Skrip Koreksi Database (`run_normalization.php`)**
   - Skrip khusus dijalankan untuk menggantikan nilai-nilai invalid yang bersarang di tabel `project_produk` dengan total realokasi yang dibentuk murni dari `project_sub_tasklist_komposisi`.
   - **Dampak**: Total 116 Project berhasil dikoreksi dan diturunkan angkanya kembali ke perhitungan riil (seperti Project 140 yang turun dari 100.68% ke 43.18%).

---

## 16 Juli 2026 - Penyelarasan Sisa Anggaran dan Pencegahan Pengembalian Saldo (Return) pada SPK Utama & Tambahan

### 🔴 Permasalahan
1. Terdapat ketidaksesuaian hitungan sisa material pada item yang memiliki nilai Return (contohnya: BRACKET HODA di Project 179). Halaman overview menunjukkan `6`, sedangkan modal penugasan menampilkan negatif `-3`. Hal ini mengunci tombol simpan.
2. Ketika pelaksana melaporkan progres selesai dengan nilai return (`jml_return > 0`), sistem secara otomatis menambahkan kembali kuantitas return tersebut ke kolom `qty_saldo` (sisa budget) di Master RAB (`jenis_transaksi = '5582'`). Hal ini melanggar aturan bisnis bahwa return tidak boleh memengaruhi sisa alokasi anggaran proyek utama.

### 🔍 Akar Masalah
1. **Ketidaksesuaian Rumus Sisa**: Javascript modal penugasan secara keliru mengurangkan return dari saldo sisa.
2. **Koreksi Footer Total B.O.M**: Footer total untuk kolom B.O.M di modal penugasan menjumlahkan `qty_saldo` bukannya `bom`.
3. **Pengembalian Saldo di Database (Refund)**: Di dalam `exeSubTasklist()`, terdapat blok kode penyesuaian stok produk yang mengembalikan `return_jml` ke dalam `qty_saldo` Master RAB dan mengurangi `qty_kredit`, sehingga merusak nilai sisa budget riil di DB.

### 🛠️ Solusi & Perbaikan
1. **Penyelarasan JS Modal**: Mengubah formula `saldo` di modal penugasan agar tidak mengurangi `jml_return` (menggunakan sisa alokasi `qty_saldo` secara langsung).
2. **Perbaikan Footer Total B.O.M**: Mengubah akumulator `total_qty` pada footer modal agar menjumlahkan `bom*1` alih-alih `jml*1`.
3. **Pemberhentian Refund Saldo**: Mengomentari seluruh blok `//region membalikan stok pada sub work order` di dalam `exeSubTasklist()` agar saldo anggaran Master RAB (`qty_saldo` dan `qty_kredit`) tetap terkunci pada nilai alokasi awal penugasan SPK (berlaku untuk SPK Utama dan SPK Tambahan). Kuantitas return tetap tercatat secara historis di tabel `project_sub_tasklist_komposisi` untuk pelacakan fisik logistik.

### 📄 File yang Di-upgrade (Modified)

1. **`application/modules/master_project/controllers/MasterData.php`**
   - Baris 6349: Mengubah `saldo = (b.qty_saldo*1 - b.jml_return*1 );` menjadi `saldo = b.qty_saldo*1;`.
   - Baris 6394: Mengubah `total_qty += jml*1;` menjadi `total_qty += bom*1;`.
   - Baris 20209: Mengomentari blok update Master RAB `qty_saldo` / `qty_kredit` berdasarkan `return_jml` di fungsi `exeSubTasklist()`.
   - Baris 20617: Menggantikan seluruh blok cURL HTTP loopback dengan eksekusi lokal in-memory menggunakan library `SuppliesReturnService` dan `FgReturnService`. Nilai return (termasuk 0) tetap selalu dikirim ke logistik untuk pencatatan pengakuan penggunaan barang proyek.
   - Baris 24863: Merefaktor fungsi `postLateReturn()` dan `tool_createReturnBySPK()` untuk menggunakan library return lokal secara in-memory. Menghilangkan ketergantungan variabel tidak terdefinisi pada `postLateReturn()` dan memisahkan penanganan response error serta pembaharuan status `supplies_gagal` / `produk_gagal` pada tabel `project_log_return`.

2. **`application/libraries/SuppliesReturnService.php`** [NEW]
   - Berkas library return supplies logistik in-memory.

3. **`application/libraries/FgReturnService.php`** [NEW]
   - Berkas library return produk logistik in-memory.

