# Implementation Plan: Optimasi `followupPreview` — master_project

## Goal
Mempercepat `followupPreview` dengan menghilangkan query database duplikat yang datanya sudah tersimpan di `$_SESSION` oleh `followupPrePreview`.

## Pendekatan: Session-First dengan DB Fallback

Strategi ini aman karena:
- Jika `followupPrePreview` sudah jalan → data ada di session → skip query, baca dari session
- Jika `followupPreview` dipanggil langsung (misal reload, atau dari `_followupLiveEdit`) → session sudah ada dari panggilan sebelumnya → tetap pakai session
- Jika session kosong (edge case) → fallback ke query DB seperti sekarang

---

## Proposed Changes

### Fase 1 — Simpan Data Tambahan di Session (followupPrePreview)

#### [MODIFY] [FollowUp.php](file:///w:/everest_6sept/application/modules/master_project/controllers/FollowUp.php)

**Di `followupPrePreview` (sekitar line 1770-1810, region "session swapper"):**
Tambahkan penyimpanan `$tmpTr` dan `$signNumbers` ke session agar `followupPreview` bisa membacanya tanpa query ulang.

```diff
 // Pada akhir region session-swapper di followupPrePreview, sebelum redirect:
+$_SESSION[$cCode]['_cache_tmpTr'] = serialize($tmpTr);
+$_SESSION[$cCode]['_cache_signNumbers'] = $signNumbers;
```

> [!NOTE]
> `$tmpTr` berisi array of objects dari `lookupJoined()` — perlu di-serialize karena PHP session tidak selalu menyimpan objects dengan baik lintas request.

---

### Fase 2 — Gunakan Session Cache di followupPreview

#### [MODIFY] [FollowUp.php](file:///w:/everest_6sept/application/modules/master_project/controllers/FollowUp.php)

**2a. `lookupJoined` → baca dari session (line 1976-1982)**

```diff
 //region read items from existing model
 $this->load->model("MdlTransaksi");
 $tr = new MdlTransaksi();
-$tr->setFilters(array());
-$tr->addFilter("id in (" . implode(",", explode("-", $no)) . ")");
-$tr->addFilterJoin("sub_step_number='" . $currentStepNum . "'");
-$tmpTr = $tr->lookupJoined();
+// OPTIMASI: baca dari cache session jika tersedia (diisi oleh followupPrePreview)
+if (isset($_SESSION[$this->cCode]['_cache_tmpTr'])) {
+    $tmpTr = unserialize($_SESSION[$this->cCode]['_cache_tmpTr']);
+} else {
+    $tr->setFilters(array());
+    $tr->addFilter("id in (" . implode(",", explode("-", $no)) . ")");
+    $tr->addFilterJoin("sub_step_number='" . $currentStepNum . "'");
+    $tmpTr = $tr->lookupJoined();
+}
```

**2b. `lookupSignaturesByMasterID` → baca dari session (line 1984-2000)**

```diff
 //region SIGNATURE
 $signNumbers = array();
-$trs = new MdlTransaksi();
-$trs->setFilters(array());
-$tmpSign = $trs->lookupSignaturesByMasterID($no)->result();
-if (sizeof($tmpSign) > 0) {
-    $sCtr = 0;
-    foreach ($tmpSign as $row) {
-        $signNumbers[$sCtr] = "" . $row->step_number;
-        $sCtr++;
-    }
+// OPTIMASI: baca dari cache session jika tersedia
+if (isset($_SESSION[$this->cCode]['_cache_signNumbers'])) {
+    $signNumbers = $_SESSION[$this->cCode]['_cache_signNumbers'];
+} else {
+    $trs = new MdlTransaksi();
+    $trs->setFilters(array());
+    $tmpSign = $trs->lookupSignaturesByMasterID($no)->result();
+    if (sizeof($tmpSign) > 0) {
+        $sCtr = 0;
+        foreach ($tmpSign as $row) {
+            $signNumbers[$sCtr] = "" . $row->step_number;
+            $sCtr++;
+        }
+    }
 }
 //endregion
```

**2c. `lookupMainValuesByTransID` + `lookupDetailValuesByTransID` → baca dari session (line 2064-2081)**

```diff
 //region tabel2 tarikan untuk kolom2 nilai (hpp, ppn, dll)
-$tmpVal_main = $tr->lookupMainValuesByTransID($trID)->result();
-$tmpVal_detail = $tr->lookupDetailValuesByTransID($trID)->result();
-$mainValues = array();
-if (sizeof($tmpVal_main) > 0) {
-    foreach ($tmpVal_main as $row) {
-        $mainValues[$row->key] = $row->value;
+// OPTIMASI: baca dari session jika sudah diisi oleh followupPrePreview
+if (isset($_SESSION[$cCode]['tableIn_master_values']) && sizeof($_SESSION[$cCode]['tableIn_master_values']) > 0) {
+    $mainValues = $_SESSION[$cCode]['tableIn_master_values'];
+} else {
+    $tmpVal_main = $tr->lookupMainValuesByTransID($trID)->result();
+    $mainValues = array();
+    if (sizeof($tmpVal_main) > 0) {
+        foreach ($tmpVal_main as $row) {
+            $mainValues[$row->key] = $row->value;
+        }
     }
 }
-$detailValues = array();
-if (sizeof($tmpVal_detail) > 0) {
-    foreach ($tmpVal_detail as $row) {
-        $detailValues[$row->produk_id][$row->key] = $row->value;
+if (isset($_SESSION[$cCode]['tableIn_detail_values']) && sizeof($_SESSION[$cCode]['tableIn_detail_values']) > 0) {
+    $detailValues = $_SESSION[$cCode]['tableIn_detail_values'];
+} else {
+    $tmpVal_detail = $tr->lookupDetailValuesByTransID($trID)->result();
+    $detailValues = array();
+    if (sizeof($tmpVal_detail) > 0) {
+        foreach ($tmpVal_detail as $row) {
+            $detailValues[$row->produk_id][$row->key] = $row->value;
+        }
     }
 }
 //endregion
```

**2d. `lookupExtSteps` + `lookupPaymentSrcs` duplikat → hapus (line 3562-3568)**

Ini sudah diisi di session oleh PrePreview dan bahkan di awal Preview sendiri. Di line 3563-3568, kedua query ini dipanggil LAGI dan hasilnya di-overwrite ke session yang sama. Hapus:

```diff
-//==ini dua benda ini dibikin ulang di sini karena nantinya harus selalu refresh tanpa memanggil PrePreview lagi
-$trA = new MdlTransaksi();
-$extSteps = $trA->lookupExtSteps($masterID);
-$paySrcs = $trA->lookupPaymentSrcs($masterID, $this->jenisTr . "_");
-
-$_SESSION[$cCode]['extSteps'] = $extSteps;
-$_SESSION[$cCode]['paySrcs'] = $paySrcs;
+// OPTIMASI: extSteps dan paySrcs sudah diisi di session oleh followupPrePreview
+// Tidak perlu query ulang — gunakan yang sudah ada di session
```

---

### Fase 3 — Hapus fillValues Duplikat

#### [MODIFY] [FollowUp.php](file:///w:/everest_6sept/application/modules/master_project/controllers/FollowUp.php)

Di sekitar line 1934-1935 (di `followupPrePreview`), terdapat 2 pemanggilan `fillValues_he_value_builder` identik berurutan. Hapus yang kedua.

> [!IMPORTANT]
> Perlu verifikasi apakah memang duplikat. Saya akan periksa konteks sekitarnya saat eksekusi.

---

## Open Questions

> [!IMPORTANT]
> 1. **Apakah ada skenario di mana `followupPreview` dipanggil langsung tanpa `followupPrePreview`?** Berdasarkan analisis, skenario utamanya adalah setelah `_followupLiveEdit` (edit item, update field, dll). Di kasus tersebut, session sudah ada dari panggilan PrePreview sebelumnya. Fallback DB tetap tersedia untuk keamanan. **Apakah ini asumsi yang benar?**
>
> 2. **Region DETAILS BOM (line 4683-5763)** melakukan ~15 query project-specific (komposisi, work order, stock locker, payment summary). Ini juga berkontribusi signifikan terhadap latensi. **Apakah Anda ingin saya optimasi section ini juga di fase selanjutnya?**

---

## Verification Plan

### Manual Verification
1. Akses `followupPrePreview` → pastikan redirect ke `followupPreview` berhasil dan data tampil normal
2. Dari halaman `followupPreview`, lakukan live edit (ubah qty, hapus item) → pastikan data tetap konsisten
3. Akses langsung `followupPreview` via URL (tanpa PrePreview) → pastikan fallback DB bekerja
4. Bandingkan waktu loading sebelum dan sesudah optimasi
