# Optimasi DataTable Reload pada Opname Approval

## Konteks Masalah

Saat ini pada halaman approval opname (step 1 `*ro` dan step 2 final), setiap kali user melakukan:
1. **Checklist serial number** — `tableOpname.ajax.reload(null, false)` dipanggil (debounce 800ms)
2. **Perubahan qty_opname atau harga** — `tableOpname.ajax.reload(null, false)` langsung dipanggil setelah AJAX sukses

DataTable dikonfigurasi **server-side** (`serverSide: true`), sehingga setiap `.ajax.reload()` memicu request POST ke `fetchCartDataTable()` — sebuah endpoint berat (~800 baris logika) yang:
- Membaca seluruh session items
- Melakukan batch query serial dari `_rek_pembantu_produk_perserial_cache`
- Menghitung IRA score, selisih, dan 20+ summary metrics
- Merender ulang HTML serial checkbox untuk child rows

### Dampak UX Saat Ini
- **Flicker visual**: Seluruh tabel ter-render ulang → child rows (serial) collapse lalu expand lagi
- **Input focus hilang**: User kehilangan posisi cursor saat mengedit qty berturut-turut
- **Checkbox state visual reset**: Styling checkbox serial (bold/hijau) reset setelah reload
- **Beban server berlebih**: Request berat berulang untuk perubahan data yang sebenarnya minimal

---

## Analisis Kelayakan: Bisa Dihilangkan?

### ✅ Serial Checklist → **BISA DIHILANGKAN** (DOM-only update)

**Alasan:**
- Response dari `updateSerial()` adalah JSON: `{status, id, sku, serial, state}` — hanya konfirmasi
- Frontend **sudah** melakukan local styling (`elChk.closest('label').css(...)`) dan **sudah** memanggil `updateWadah()` yang menghitung ulang badge PAS/KURANG/LEBIH secara lokal
- Satu-satunya alasan reload adalah untuk update **summary panel** (serial_mismatch_count, tombol approve lock/unlock)
- **Solusi**: Minta server mengembalikan data summary serial terbaru dalam response `updateSerial()`, lalu update summary panel secara lokal tanpa reload tabel

### ✅ Qty/Price Change → **BISA DIHILANGKAN** (DOM-only update)

**Alasan:**
- Response dari `updateItemFieldOpname(silent=1)` sudah mengembalikan: `{status, id, selisih, qty_debet, qty_kredit, subtotal}`
- Frontend **sudah** melakukan in-place DOM update untuk kolom selisih/debet/kredit (baris 4667-4681)
- Reload dipanggil **setelah** DOM update lokal, sehingga redundan — hasil DOM lokal langsung ditimpa oleh reload
- **Solusi**: Perluas response server dengan data summary (total_debet, total_kredit, total_subtotal), lalu update footer dan summary panel secara lokal

---

## Proposed Changes

### Komponen 1: Backend — Perkaya Response `updateSerial()`

#### [MODIFY] [_followupLiveEdit.php](file:///z:/everest_6sept/application/modules/opname/controllers/_followupLiveEdit.php)

**Fungsi `updateSerial()` (baris 2119-2121)**:
Tambahkan perhitungan `serial_mismatch_count` dan `serial_checked_count` ke dalam JSON response, sehingga frontend bisa memperbarui:
- Badge serial compliance (PAS/KURANG/LEBIH)
- Banner kepatuhan serial (sumSerialStatus*)
- Tombol approve lock/unlock state

**Perubahan spesifik:**
Sebelum `echo json_encode(...)`, hitung:
1. Loop `items5_sum` untuk mendapatkan total checked per produk per SKU
2. Bandingkan dengan `items[pid]['qty_kredit']` (target selisih) untuk menghitung mismatch
3. Sertakan `serial_mismatch_count`, `serial_total_sku`, `items5_checked_for_pid` (checked count per produk yang diubah) dalam response

**Estimasi tambahan kode: ~50 baris**

---

### Komponen 2: Backend — Perkaya Response `updateItemFieldOpname()` (silent mode)

#### [MODIFY] [_followupLiveEdit.php](file:///z:/everest_6sept/application/modules/opname/controllers/_followupLiveEdit.php)

**Fungsi `updateItemFieldOpname()` (baris 1159-1171)**:
Tambahkan data summary ke JSON response silent mode:
- `total_harga_master` (total harga keseluruhan dari session main)
- `total_debet_sum`, `total_kredit_sum`, `total_subtotal_sum` (jumlah kolom footer)
- `has_serial` (apakah item ini produk berserial, untuk update wadah)
- `jml_serial` (jumlah serial yang diharapkan, jika produk berserial)

**Estimasi tambahan kode: ~25 baris**

---

### Komponen 3: Frontend — Hilangkan `tableOpname.ajax.reload()` dari Serial Handler

#### [MODIFY] [transaksi.php](file:///z:/everest_6sept/application/modules/opname/views/transaksi.php)

**Baris 4469-4477 (Serial checkbox handler di `drawCallback`)**:
```
// SEBELUM:
$.get(link, { state: isChk, sku: sku, id: id, serial: serial }, function() {
    clearTimeout(serialDebounceTimer);
    serialDebounceTimer = setTimeout(function() {
        tableOpname.ajax.reload(null, false);   // ← DIHAPUS
    }, 800);
});

// SESUDAH:
$.get(link, { state: isChk, sku: sku, id: id, serial: serial }, function(res) {
    // Update summary panel serial compliance dari response server
    updateSerialComplianceSummary(res);
}, 'json');
```

**Tambah fungsi baru `updateSerialComplianceSummary(res)`** (~40 baris):
- Menerima JSON dari `updateSerial()` yang sudah diperkaya
- Update `#sumSerialStatusIcon`, `#sumSerialStatusTitle`, `#sumSerialStatusDesc`
- Update tombol approve (lock/unlock berdasarkan mismatch count)
- Update `#btnQuickFilterMismatch` text
- Set `window.latestOpnameSummary.serial_mismatch_count = res.serial_mismatch_count`

---

### Komponen 4: Frontend — Hilangkan `tableOpname.ajax.reload()` dari Qty/Price Handler

#### [MODIFY] [transaksi.php](file:///z:/everest_6sept/application/modules/opname/views/transaksi.php)

**Baris 4707-4714 (debounce handler) dan baris 4745-4752 (change/blur handler)**:
```
// SEBELUM:
success: function(res) {
    lastSyncedVal[syncKey] = rawVal;
    elStatus.html('...');
    updateGlobalBadge('saved');
    if (key === 'qty_opname' || key === 'harga') {
        tableOpname.ajax.reload(null, false);   // ← DIHAPUS
    }
}

// SESUDAH:
success: function(res) {
    lastSyncedVal[syncKey] = rawVal;
    elStatus.html('...');
    updateGlobalBadge('saved');
    if (key === 'qty_opname' || key === 'harga') {
        updateRowFromSilentResponse(elRow, res, key);  // ← LOCAL DOM UPDATE
    }
}
```

**Tambah fungsi baru `updateRowFromSilentResponse(elRow, res, key)`** (~30 baris):
- Update kolom `selisih`, `qty_debet`, `qty_kredit` dari `res` pada baris tersebut (validasi silang dengan DOM update yang sudah ada)
- Update kolom `subtotal` dari `res.subtotal`
- Update footer summary: `#sum_stok`, `#sum_qty_debet`, `#sum_qty_kredit`, `#sum_subtotal` dari `res`
- Update row class (has-selisih / no-selisih)

---

### Komponen 5: Safety Mechanism — Periodic Background Sync

#### [MODIFY] [transaksi.php](file:///z:/everest_6sept/application/modules/opname/views/transaksi.php)

Tambahkan **background sync timer** sebagai jaring pengaman untuk memastikan data tetap konsisten:

```javascript
// Sinkronisasi latar belakang setiap 60 detik HANYA jika ada perubahan sejak reload terakhir
var hasLocalChanges = false;
setInterval(function() {
    if (hasLocalChanges) {
        hasLocalChanges = false;
        tableOpname.ajax.reload(null, false);
    }
}, 60000);
```

Flag `hasLocalChanges` diset `true` setiap kali serial di-check atau qty/harga diubah, dan di-reset setelah background reload berjalan. Ini memastikan data summary (IRA score, loss/gain values, filter counts) tetap akurat secara periodik tanpa mengganggu UX.

---

## User Review Required

> [!IMPORTANT]
> **Interval Background Sync**: Saya mengusulkan **60 detik**. Apakah interval ini cukup, atau perlu dipercepat (30 detik) / diperlambat (120 detik)?

> [!IMPORTANT]
> **Filter Count Badges**: Saat user mengubah qty (misalnya dari selisih 0 menjadi selisih +5), badge count filter (`#countOpnameSelisih`, `#countOpnameSelisihPlus`, dll) tidak akan langsung update sampai background sync berikutnya. Apakah ini bisa diterima, atau perlu update instan? (Update instan memerlukan server mengirim kembali seluruh summary dalam response `updateItemFieldOpname`, yang menambah beban response).

> [!WARNING]
> **Perubahan filter tab saat `serial_mismatch` aktif**: Jika user sedang di filter `serial_mismatch` dan mengecek serial sehingga mismatch-nya berkurang, baris produk yang sudah balance tidak otomatis hilang dari tabel tanpa reload. Saya sarankan: jika user sedang di filter `serial_mismatch`, tetap lakukan reload setelah serial change (karena filtered set-nya berubah).

## Open Questions

> [!NOTE]
> Apakah ada kebutuhan untuk menampilkan **running total harga master** (di luar tabel, misalnya di header/footer form) yang juga harus diupdate secara real-time saat harga berubah? Jika ya, saya perlu menambahkan field `total_harga_master` ke response `updateItemFieldOpname`.

---

## Verification Plan

### Manual Verification
1. Buka halaman approval opname (step 1 `*ro`)
2. **Test Serial**: Centang/uncentang beberapa serial number → **verifikasi**: tabel TIDAK reload, badge PAS/KURANG/LEBIH update secara instan, tombol approve lock/unlock sesuai
3. **Test Qty**: Ubah qty_opname beberapa item → **verifikasi**: tabel TIDAK reload, kolom selisih/debet/kredit update secara instan, footer summary update
4. **Test Harga**: Ubah harga item → **verifikasi**: kolom subtotal update, footer total update
5. **Test Filter**: Aktifkan filter `serial_mismatch` → centang serial → **verifikasi**: tabel RELOAD (karena filtered set berubah)
6. **Test Background Sync**: Tunggu 60 detik setelah perubahan → **verifikasi**: reload otomatis terjadi di latar belakang, semua badge count ter-update
7. **Test Multi-page**: Navigasi ke halaman 2/3 DataTable → ubah qty → **verifikasi**: tetap di halaman yang sama
8. **Test approval step 2** (final): Ulangi test 2-6 pada step final approval
