# 🏛️ MASTER BLUEPRINT, PROTOKOL FORENSIK & RENCANA PERBAIKAN ARSITEKTUR DISKON PEMBELIAN
**Sistem:** Everest ERP (CodeIgniter 3.1.8 / Wiredesignz HMVC / PHP 5.6 / MariaDB 10)  
**Dokumen Sintesis:** Elaborasi dan Kolaborasi Terpadu dari:  
1. `analisis_hulu_hilir_diskon.md` (Investigasi 4 Titik Kerusakan Sistemik & Kasus Lapangan)  
2. `ATURAN_PEMBACAAN_DATA_DISKON.md` (Protokol 3-Way Matching, Standar Agregasi & Query Safety)  
3. `rencana_perbaikan_codebase_diskon.md` (Action Plan Codebase, Arsitektur File & Script CLI Migrasi)  
**Database Operasional:** DB Utama `192.168.5.10` (`everest_26sep`) & DB AI Replica `192.168.5.17` (`run_everest_modul`)  
**Status Dokumen:** Master Reference & Implementation Blueprint (MANDATORY STANDARD)  
**Terakhir Diperbarui:** 30 September 2026  

---

## 📌 DAFTAR ISI EKSEKUTIF
1. [Ringkasan Eksekutif & Temuan Forensik](#1-ringkasan-eksekutif--temuan-forensik)
2. [Arsitektur Alur Hulu - Hilir Diskon & Peta Kerusakan (Mermaid)](#2-arsitektur-alur-hulu---hilir-diskon--peta-kerusakan)
3. [Kerangka Kerja Audit Forensik: 3-Way Matching & 5W+1H Engine](#3-kerangka-kerja-audit-forensik-3-way-matching--5w1h-engine)
4. [Anatomi Detail 4 Titik Kerusakan Sistemik (+ Anomali Retur)](#4-anatomi-detail-4-titik-kerusakan-sistemik--anomali-retur)
5. [Standar Protokol Pembacaan Data & Keselamatan Kueri (Query Safety)](#5-standar-protokol-pembacaan-data--keselamatan-kueri-query-safety)
6. [Blueprint Perbaikan Codebase Terpadu (File-by-File Guide)](#6-blueprint-perbaikan-codebase-terpadu-file-by-file-guide)
7. [Spesifikasi 3 Script CLI Rekonsiliasi Database (Database Patch)](#7-spesifikasi-3-script-cli-rekonsiliasi-database-database-patch)
8. [Protokol Keselamatan Eksekusi & Urutan Langkah Kerja (Runbook)](#8-protokol-keselamatan-eksekusi--urutan-langkah-kerja-runbook)
9. [Matriks Verifikasi & Troubleshooting Lapangan](#9-matriks-verifikasi--troubleshooting-lapangan)

---

## 1. Ringkasan Eksekutif & Temuan Forensik

Berdasarkan audit komparatif mendalam antara komitmen transaksi di modul `pembelian`, pencatatan fisik stok diskon di tabel `stock_locker_diskon`, serta pembukuan akuntansi di buku pembantu `1010020030` dan modul `kompensasiharga`, ditemukan fakta bahwa **sistem mengalami diskrepansi sistemik hulu-ke-hilir**:

### Metrik Dampak pada Database Produksi (Live Data 2024–2026):
| Indikator Permasalahan | Database 5.17 (Replika) | Database 5.10 (Master) | Status & Dampak Bisnis |
| :--- | :---: | :---: | :--- |
| **Total GRN Diskon Sehat & Selaras** | 4.985 GRN | $\approx$ 5.200 GRN | Transaksi valid, fisik locker dan jurnal seimbang. |
| **GRN Mengalami Over-Debet Jurnal** | **2.308 GRN** | $\approx$ 2.450 GRN | **Debet membengkak $2\times$ hingga $4\times$ lipat** atau mengambil total PO utuh pada GRN parsial. |
| **Fisik Locker Defisit / Ter-wipe (Diskon Hilang)** | 114 GRN | $\approx$ 125 GRN | Diskon ada di kontrak PO/Registry tapi **slot fisik bernilai Rp 0**. |
| **Klaim 3333 Tanpa Mutasi Kredit Buku Pembantu** | **0 dokumen (Telah di-Patch)** | - | **Telah dinormalisasi** (Sebelumnya 49 dok, 43 dok terjadi pada 22 Juli 2026). |
| **Anomali Retur 9911 (Locker Menggantung)** | 35 GRN | $\approx$ 40 GRN | Jurnal retur sudah menolkan piutang, tapi **saldo locker lupa di-void** (berisiko klaim ganda). |
| **Total Akumulasi Diskrepansi Nominal** | **Rp 2.441.933.245,69** | $\approx$ Rp 2,6 Miliar | Selisih semu antara fisik locker dengan saldo buku pembantu akuntansi. |

> [!CRITICAL]
> **Masalah ini aktif dan terus berulang setiap hari** pada transaksi baru. Tanpa intervensi pada level arsitektur kode (*codebase*), setiap penerimaan barang baru (GRN 467) yang memiliki diskon berpotensi terus mencatatkan over-debit atau kehilangan slot diskon fisiknya.

---

## 2. Arsitektur Alur Hulu - Hilir Diskon & Peta Kerusakan

Alur pergerakan diskon pembelian dari perjanjian pembelian sampai pelunasan melibatkan 4 fase utama. Diagram berikut memetakan perjalanan data serta letak persis kerusakan sistemiknya:

```mermaid
flowchart TD
    classDef ok fill:#dcfce7,stroke:#16a34a,color:#166534,stroke-width:2px;
    classDef err fill:#fee2e2,stroke:#dc2626,color:#991b1b,stroke-width:2px;
    classDef warn fill:#fef3c7,stroke:#d97706,color:#92400e,stroke-width:2px;
    classDef nodeStyle fill:#f8fafc,stroke:#64748b,color:#0f172a,stroke-width:1px;

    subgraph PHASE1 ["HULU 1: Purchase Order (PO 466 - Modul Pembelian)"]
        PO["Penerbitan PO 466<br/>(Contoh: PO.-1.1849 / 189 unit)"]:::ok
        REG_PO["Blob Registry PO<br/>(items4_sum & items5_sum)"]:::ok
        PRE_LOCKER["Pre-Locker Diskon PO<br/>(stock_locker_pre_diskon)"]:::err
        
        PO -->|"Simpan PO"| REG_PO
        PO -->|"PreSyncDiskonPembelian"| PRE_LOCKER
        T1["❌ TITIK RUSAK 1:<br/>Nilai pre_diskon terisi 0<br/>karena lookup formula keliru"]:::err
        PRE_LOCKER -.-> T1
    end

    subgraph PHASE2 ["HULU 2: Penerimaan Barang (GRN 467 - Modul Pembelian)"]
        GRN["Penerimaan Fisik GRN 467<br/>(Contoh: GRN.-1.2496 / 50 unit)"]:::ok
        CHK_PRE{"Cek Pre-Locker<br/>(nilai > 10?)"}:::warn
        LOCKER_DISKON["Tabel stock_locker_diskon<br/>(Fisik Hak Diskon Supplier)"]:::err
        JURNAL_CACHE["Buku Pembantu Piutang<br/>(__rek_pembantu...1010020030)"]:::err

        PO --> GRN
        GRN --> CHK_PRE
        CHK_PRE -->|"Jika Pre-Locker = 0"| RESET["Destructive Reset:<br/>diskon_1_nilai = 0<br/>Slot dieksklusikan dari items4"]:::err
        RESET --> LOCKER_DISKON
        T2A["❌ TITIK RUSAK 2A:<br/>Slot Diskon 1 Hilang di Locker<br/>(Hanya Diskon 2 & 3 terbit)"]:::err
        LOCKER_DISKON -.-> T2A

        GRN -->|"Komponen Master [99] & Detail [5]"| JURNAL_CACHE
        T2B["❌ TITIK RUSAK 2B:<br/>Duplikasi Debet (2x-4x lipat)<br/>& Debet Seluruh PO pd GRN Parsial"]:::err
        JURNAL_CACHE -.-> T2B
    end

    subgraph PHASE3 ["HILIR 1: Realisasi Klaim Diskon (3333 - Modul Kompensasi Harga)"]
        KLAIM["Input Dokumen Klaim 3333<br/>(Contoh: 3333.-1.191 & 2570)"]:::ok
        SELECTOR["Selector Nota GRN<br/>(_selectorPihakMain)"]:::warn
        MUTASI_KREDIT["Mutasi Kredit Pembantu<br/>(__rek_pembantu...1010020030)"]:::err
        GL_JURNAL["Buku Jurnal Umum (GL)<br/>(Credit Note / Kas / CN AP)"]:::ok

        GRN --> SELECTOR
        SELECTOR --> KLAIM
        KLAIM --> GL_JURNAL
        KLAIM -->|"Potong Saldo Fisik (nilai)"| LOCKER_DISKON
        KLAIM -->|"Posting Kredit Mutasi"| MUTASI_KREDIT

        T3["❌ TITIK RUSAK 3:<br/>Selector kirim ID Diskon (bukan ID GRN)<br/>-> 49 Klaim Gagal Posting Kredit"]:::err
        MUTASI_KREDIT -.-> T3
    end

    subgraph PHASE4 ["HILIR 2: Monitoring & Visualisasi (DataTable Transaksi.php)"]
        DATATABLE["DataTable viewKlaimSupplierDataAjax<br/>(Kolom: Diskon, Klaim, Sisa)"]:::err
        
        LOCKER_DISKON -->|"Sumber Utama (Fisik)"| DATATABLE
        JURNAL_CACHE -->|"Fallback jika Locker = 0"| DATATABLE
        MUTASI_KREDIT -->|"Akumulasi Klaim"| DATATABLE

        T4["❌ TITIK RUSAK 4:<br/>Locker 0 -> Jatuh ke Fallback Cache.<br/>Cache Over-debet 24,6 Jt.<br/>Hasil Layar Bengkak Rp 20,1 Jt!"]:::err
        DATATABLE -.-> T4
    end

    subgraph RETUR_PHASE ["ANOMALI PEMBATALAN (Retur Pembelian 9911)"]
        RETUR_DOC["Dokumen Retur 9911<br/>(trash_4 = 1)"]:::warn
        RETUR_JURNAL["Kredit Pembantu Piutang<br/>(Saldo Buku Menjadi 0)"]:::ok
        RETUR_LOCKER["Stock Locker Diskon<br/>(Saldo Tetap Menggantung!)"]:::err

        GRN --> RETUR_DOC
        RETUR_DOC --> RETUR_JURNAL
        RETUR_DOC -.->|"Lupa Buat Baris Kontra"| RETUR_LOCKER
        T_RETUR["⚠️ ANOMALI RETUR:<br/>Locker Aktif Menggantung<br/>Rawan Dicairkan Ganda"]:::err
        RETUR_LOCKER -.-> T_RETUR
    end
```

---

## 3. Kerangka Kerja Audit Forensik: 3-Way Matching & 5W+1H Engine

Untuk menjamin tidak ada lagi bias interpretasi antara bagian gudang/logistik dengan bagian akuntansi/keuangan, sistem audit Everest mengadopsi standar rekonsiliasi matematis terpadu:

### 3.1 Tiga Pilar 3-Way Matching
1. **Pilar 1 (Hulu - Komitmen PO):** Berapa hak diskon yang disepakati dengan supplier pada kontrak pembelian?
   $$\text{Expected Diskon} = \sum (\text{Qty Diterima} \times \text{Tarif Diskon Satuan})$$
2. **Pilar 2 (Fisik - Logistik GRN):** Berapa nilai fisik diskon yang benar-benar diterbitkan pada wadah stok diskon?
   $$\text{Total Terbit Locker} = \sum [(\text{nilai} + \text{IFNULL}(\text{nilai\_diklaim}, 0)) \times \text{IF}(\text{jumlah} < 0, -1, 1)]$$
3. **Pilar 3 (Hilir - Pembukuan Akuntansi & Klaim):** Berapa nilai hak yang diakui di buku pembantu dan berapa yang telah dicairkan?
   $$\text{Debet Jurnal Piutang} = \sum (\text{debet pada COA } 1010020030)$$
   $$\text{Kredit Realisasi Klaim} = \sum (\text{kredit dari dokumen } 3333)$$

### Kriteria Status Transaksi:
* 🟢 **MATCHED (Valid & Selaras):**
  $$\text{Pilar 1} == \text{Pilar 2} == \text{Debet Pilar 3} \quad (\text{toleransi } \le \text{Rp } 100)$$
  $$\text{Klaim Locker} == \text{Kredit Jurnal Realisasi}$$
* 🔴 **OVER-DEBET JURNAL (Titik Rusak 2B):**
  $$\text{Debet Jurnal} > 1.2 \times \text{Total Terbit Locker}$$
* ⚠️ **LOCKER DEFISIT / TER-WIPE (Titik Rusak 2A):**
  $$\text{Total Terbit Locker} < \text{Expected Diskon} - 100$$
* ⚠️ **KLAIM DESINKRONISASI (Titik Rusak 3):**
  $$|\text{Klaim Locker} - \text{Kredit Jurnal}| > 100$$
* ⚠️ **RETUR MENGGANTUNG (Anomali Pembatalan):**
  $$\text{Saldo Buku} == 0 \quad \text{DAN} \quad \text{Saldo Locker} > 100 \quad \text{DAN} \quad \text{Ada Dokumen } 9911$$

### 3.2 Kerangka Investigasi Forensik 5W + 1H
| Elemen | Dimensi Investigasi | Pertanyaan Kunci yang Terjawab Secara Sistem |
| :--- | :--- | :--- |
| **WHAT** | Status Ketersediaan Hak | Apakah diskon masih ada (aktif), habis terklaim sah, ter-wipe (0 tanpa klaim), atau menggantung akibat retur? |
| **WHO** | Pihak yang Terlibat | Siapa suppliernya (ID & Nama), petugas penerima barang gudang (GRN), dan kasir yang memproses klaim 3333? |
| **WHERE** | Jejak Audit 4 Lapis | Pada tabel mana diskrepansi mulai terjadi? (`stock_locker_pre_diskon` &rarr; `stock_locker_diskon` &rarr; `__rek_pembantu...` &rarr; `jurnal` GL). |
| **WHEN** | Kronologi & Penuaan (*Aging*) | Tanggal PO &rarr; Tanggal GRN &rarr; Tanggal Debet Jurnal &rarr; Tanggal Realisasi Klaim 3333. Berapa hari rentang klaimnya? |
| **WHY** | Analisis Kausalitas | Apa penyebab teknisnya? Apakah akibat reset di `PreSyncDiskonPembelian`, duplikasi komponen di `coTransaksiCore`, atau mismatch di `coTransaksiUi`? |
| **HOW** | Tindakan Rekonsiliasi | Bagaimana cara menormalkannya? Apakah via normalisasi debet cache, restorasi slot locker, atau suntik mutasi kredit susulan? |

---

## 4. Anatomi Detail 4 Titik Kerusakan Sistemik (+ Anomali Retur)

### 🔴 TITIK RUSAK 1: Plafon Awal Pre-Locker PO Bernilai 0
* **Lokasi File:** `application/models/Preprocs/PreSyncDiskonPembelian.php`
* **Gejala:** Pada saat PO diterbitkan, seluruh baris di `stock_locker_pre_diskon` terisi `nilai = 0.0000000000` (contoh kasus PO `383654` / `PO.-1.1849`).
* **Akar Masalah:** Filter kueri pencarian pre-diskon mengasumsikan nilai sudah ada di locker sebelum transaksi disimpan, atau query lookup gagal memetakan id supplier/cabang sehingga menghasilkan nilai `0`.
* **Dampak Berantai:** Nilai 0 ini menjadi *trigger* kondisi destruktif pada fase penerimaan barang (GRN).

### 🔴 TITIK RUSAK 2A: Slot Fisik Locker Ter-Wipe / Hilang saat Simpan GRN
* **Lokasi File:** `application/models/Preprocs/PreSyncDiskonPembelian.php` baris 276–284 & 327.
* **Gejala:** Dokumen GRN memiliki hak diskon pada kontrak/registry barang, tetapi slot barisnya di tabel `stock_locker_diskon` **TIDAK PERNAH TERBIT** (contoh: Diskon 1 pada `GRN.-1.2496`).
* **Akar Masalah:** Pada baris 327 terdapat pengecekan:
  ```php
  $m->addFilter("transaksi_id='$po_id'");
  $m->addFilter("nilai>'10'");
  $tmp = $m->lookUpAll()->result();
  ```
  Karena pre-diskon PO bernilai 0 (Titik 1), query mengembalikan kosong. Sistem kemudian masuk ke blok `else`:
  ```php
  $key_reset = "diskon_$diskon_key_id" . "_nilai";
  $_SESSION[$cCode][$src_key][$pID][$key_reset] = 0;
  $_SESSION[$cCode][$src_key][$pID]["sub_" . $key_reset] = 0;
  ```
  Secara destruktif, sistem menolkan nilai diskon pada sesi, sehingga Diskon 1 dieksklusikan dari `items4_sum`. Akibatnya, `ComLockerDiskonValue` tidak pernah membuat slot baris Diskon 1 di `stock_locker_diskon`.

### 🔴 TITIK RUSAK 2B: Over-Debet Buku Pembantu Jurnal Akuntansi (1010020030)
* **Lokasi File:** `application/modules/pembelian/config/coTransaksiCore.php` (step `467`)
* **Gejala:** Sebanyak 2.308 GRN memiliki nilai debet buku pembantu $2\times$ hingga $4\times$ lipat dari nilai barang, atau mencatatkan seluruh total diskon PO utuh pada penerimaan parsial pertama.
* **Akar Masalah:**
  1. **Duplikasi Komponen Master & Detail:** Komponen master `[98]` dan `[99]` mendebet COA `1010020030` menggunakan variabel `main['diskon_nilai_total']`, sementara komponen detail `[3]`, `[4]`, `[5]`, `[6]` juga mendebet rekening yang sama per baris barang dari `items4_sum['sub_diskon_nilai']`. Keduanya berjalan bersamaan sehingga terjadi pengakuan debet berganda.
  2. **Lump-Sum Diskon PO pada GRN Parsial:** Pada pengiriman bertahap (misal 50 unit Sharp dari 189 unit PO), variabel `main['diskon_nilai_total']` menyalin angka total seluruh PO (Rp 24.628.767) alih-alih proporsional terhadap 50 unit barang yang baru datang (Rp 12.076.285).

### 🔴 TITIK RUSAK 3: Mismatch Selector ID pada Realisasi Klaim 3333
* **Lokasi File:** `application/modules/kompensasiharga/config/coTransaksiUi.php` baris 2610
* **Gejala:** Ditemukan 49 dokumen klaim `3333` yang berhasil memotong saldo fisik locker, tetapi **GAGAL memposting mutasi kredit di buku pembantu piutang supplier** (43 transaksi di antaranya terjadi serentak pada 22 Juli 2026).
* **Akar Masalah:**
  ```php
  // Konfigurasi Lama yang Cacat:
  "pihakNameMainDiskonIdSelector" => "id",
  "pihakNameMainDiskonIdProcessor" => "id",
  ```
  Karena model selector adalah `MdlSupplierDiskon`, kolom `id` menghasilkan ID baris jenis diskon (`1` s/d `8`), bukan ID transaksi GRN (`34662`). Ketika disimpan, parameter yang terkirim ke `pihakMainID` adalah `2`. Saat komponen `ComRekeningPembantuPiutangSupplierDetailTransItem` memposting kredit mutasi:
  ```sql
  WHERE extern_id = '2' AND extern2_id = '' AND extern3_id = '11'
  ```
  Query tidak menemukan data karena GRN dicatat dengan `extern_id = 34662`. Akibatnya, eksekusi mutasi kredit *silent-fail* (tidak terbentuk sama sekali).

### 🔴 TITIK RUSAK 4: Fallback Monitoring DataTable yang Menampilkan Angka Bengkak
* **Lokasi File:** `application/modules/kompensasiharga/controllers/Transaksi.php` baris 6345–6385
* **Gejala:** Layar DataTable Kompensasi Harga menampilkan sisa diskon sebesar Rp 20.185.738 (padahal seharusnya Rp 7.633.256).
* **Akar Masalah:** Developer membuat logika fallback: jika di `stock_locker_diskon` nilainya 0, sistem membaca saldo dari `_rek_pembantu_subpiutangsuppliertrans_cache`. Karena slot Diskon 1 hilang di locker (Titik 2A), sistem fallback ke tabel cache. Namun tabel cache tersebut sudah terdebet ganda/over-debit Rp 24,6 Jt (Titik 2B). Hasilnya:
  $$\text{Debet } 24.628.767 - \text{Klaim } 4.443.029 = \mathbf{\text{Rp } 20.185.738}$$

### ⚠️ ANOMALI KHUSUS: Pembatalan Retur Pembelian (9911) Belum Melepas Locker Fisik
* **Lokasi Modul:** `returpembelian` (Transaksi `9911` / `trash_4 = 1`)
* **Gejala:** Transaksi GRN telah dibatalkan atau diretur penuh, jurnal akuntansi telah mengkredit piutang kembali ke Rp 0, namun saldo di `stock_locker_diskon` masih tercatat aktif utuh.
* **Akar Masalah:** Modul retur pembelian memposting mutasi pembatalan akuntansi, tetapi tidak menginstruksikan modul locker untuk menerbitkan baris pembalik kontra (`jumlah = -1`) atau menolkan kolom `nilai`. Hal ini membuka celah fatal di mana petugas dapat mencairkan klaim atas barang yang sudah diretur.

---

## 5. Standar Protokol Pembacaan Data & Keselamatan Kueri (Query Safety)

Untuk memastikan konsistensi seluruh modul, laporan, dan script maintenance, setiap interaksi database yang berkaitan dengan diskon pembelian WAJIB mematuhi protokol berikut:

### 5.1 Dekoder Blob Registry PHP 5.6 Safe (64-Bit Compatibility)
Unserialize bawaan PHP 5.6 pada OS Windows/Linux arsitektur 64-bit sering mengalami *corrupted payload* jika string blob memuat representasi integer 64-bit besar (`i:10000000000;`).
```php
// FUNGSI STANDAR DEKODER REGISTRY (WAJIB DIGUNAKAN DI SEMUA MODUL)
$fnDecodeRegistry = function($blob) {
    if (empty($blob)) return array();
    $decoded = base64_decode($blob);
    if (!$decoded) return array();
    
    // Normalisasi integer besar 64-bit menjadi string agar unserialize tidak crash
    $fixed = preg_replace_callback('/i:(\d{10,});/', function($m) {
        return 's:' . strlen($m[1]) . ':"' . $m[1] . '";';
    }, $decoded);
    
    $res = @unserialize($fixed);
    return is_array($res) ? $res : array();
};
```

### 5.2 Formula Baku Perhitungan Fisik Locker (`stock_locker_diskon`)
* **DILARANG MENGGUNAKAN `nilai_unit`:** Kolom `nilai_unit` adalah nilai nominal statis per-satuan pada saat awal terbit. Nilai tersebut tidak berkurang saat terjadi klaim atau retur.
* **SALDO AKTIF HANYA ADA DI KOLOM `nilai`:**
```sql
-- FORMULA RESMI AGREGASI SALDO LOCKER DISKON
SELECT 
    -- 1. Total Hak Awal Terbit (Memperhitungkan Pembalik Kontra Retur)
    SUM((nilai + IFNULL(nilai_diklaim, 0)) * IF(jumlah < 0, -1, 1)) AS total_locker_terbit,
    
    -- 2. Sisa Hak Aktif yang Sah Dicairkan
    SUM(nilai * IF(jumlah < 0, -1, 1)) AS sisa_locker_aktif,
    
    -- 3. Akumulasi Realisasi Pemotongan Klaim 3333
    SUM(IFNULL(nilai_diklaim, 0) * IF(jumlah < 0, -1, 1)) AS total_locker_klaim
FROM stock_locker_diskon
WHERE (transaksi_id = 34662 OR nomer = 'GRN.-1.2496')
  AND jenis_locker = 'stock' 
  AND jenis = 'diskon';
```

### 5.3 Relasi Kunci Dokumen Ganda (`transaksi_id OR nomer`)
Data lama atau data hasil impor migrasi database terkadang tidak mencatat `transaksi_id` dengan benar (terisi 0 atau ID lama). Kueri relasi fisik locker WAJIB menggunakan klausa ganda:
```sql
WHERE (transaksi_id = $grnId OR nomer = '$grnNomer')
```

### 5.4 Deteksi Tabel Sharded vs Un-Sharded Buku Pembantu
Sistem Everest mempartisi buku pembantu per kode COA:
* Tabel Prioritas: `__rek_pembantu_subpiutangsuppliertrans__1010020030`
* Tabel Fallback 1: `_rek_pembantu_subpiutangsuppliertrans` (jika tabel sharded belum terbentuk)
* Tabel Fallback 2: `_rek_pembantu_subpiutangsuppliertrans_cache` (`WHERE periode = 'forever'`)

### 5.5 Larangan Mutlak Komentar PHP di Dalam String SQL Double-Quotes
> [!CAUTION]
> **DILARANG KERAS** menuliskan komentar PHP single-line `// ...` di dalam string kueri SQL PHP (`$sql = " ... ";`).  
> PHP tidak akan membuang tanda `//` di dalam kutip ganda, sehingga MariaDB membacanya sebagai sintaks teks dan melempar **Error 1064 (Syntax Error)**. Jika ingin memberi komentar di dalam string SQL, gunakan format komentar SQL MariaDB: `-- komentar` atau `/* komentar */`.

---

## 6. Blueprint Perbaikan Codebase Terpadu (File-by-File Guide)

Perbaikan codebase difokuskan pada penguncian hulu agar transaksi baru tidak lagi rusak, serta normalisasi tampilan hilir:

### 6.1 Modul Pembelian: `PreSyncDiskonPembelian.php` (Anti-Reset Destruktif)
* **Path:** `application/models/Preprocs/PreSyncDiskonPembelian.php`
* **Logika Perbaikan:**
  Hapus aksi reset nilai diskon menjadi 0 saat pre-diskon bernilai kosong. Jika pre-diskon PO tidak ditemukan, ambil data persentase diskon langsung dari registry PO atau kontrak master supplier, lalu hitung secara proporsional terhadap quantity GRN yang diterima:
```php
// START OF COMPLETE REPEATED LOGIC
// Gantikan blok baris 276-284 di PreSyncDiskonPembelian.php:
if (!empty($tmp)) {
    // Jalur normal jika pre-diskon valid
    $_SESSION[$cCode][$src_key][$pID][$key_reset] = $tmp[0]->nilai;
} else {
    // JANGAN RESET KE 0! Ambil hak proporsional dari registry PO
    $poDiscountPct = isset($poItems4[$pID]["diskon_{$diskon_key_id}_persen"]) ? floatval($poItems4[$pID]["diskon_{$diskon_key_id}_persen"]) : 0;
    $hargaBeliUnit = isset($currentItems[$pID]['harga']) ? floatval($currentItems[$pID]['harga']) : 0;
    
    if ($poDiscountPct > 0 && $hargaBeliUnit > 0) {
        $calculatedDiskonUnit = ($poDiscountPct / 100) * $hargaBeliUnit;
        $_SESSION[$cCode][$src_key][$pID][$key_reset] = $calculatedDiskonUnit;
        $_SESSION[$cCode][$src_key][$pID]["sub_" . $key_reset] = $calculatedDiskonUnit * $qtyDiterima;
    } else {
        // Hanya nolkan jika pada kontrak PO memang tidak ada persentase diskon
        $_SESSION[$cCode][$src_key][$pID][$key_reset] = 0;
        $_SESSION[$cCode][$src_key][$pID]["sub_" . $key_reset] = 0;
    }
}
// END OF COMPLETE REPEATED LOGIC
```

### 6.2 Modul Pembelian: `coTransaksiCore.php` (Anti-Overdebit & Proporsionalitas)
* **Path:** `application/modules/pembelian/config/coTransaksiCore.php` (step `467`)
* **Logika Perbaikan:**
  1. Hapus pemicu debet COA `1010020030` pada level master `[98]` dan `[99]`. Biarkan debet hanya dieksekusi oleh Detail Trans Item `[5]` (`RekeningPembantuPiutangSupplierDetailTransItem`) yang membaca langsung dari `items4_sum`.
  2. Pastikan variabel `main['diskon_nilai_total']` untuk GRN parsial dihitung dari penjumlahan item barang yang diterima pada GRN tersebut saja, bukan menyalin total diskon PO utuh.

### 6.3 Modul Kompensasi Harga: `coTransaksiUi.php` (Koreksi Selector ID Klaim)
* **Path:** `application/modules/kompensasiharga/config/coTransaksiUi.php` (baris 2610)
* **Logika Perbaikan:**
  Ubah field selector dari `id` menjadi `transaksi_id` agar parameter `pihakMainID` membawa ID transaksi GRN yang valid:
```php
// START OF COMPLETE REPEATED LOGIC
"pihakNameMainDiskonIdSelector" => "transaksi_id",
"pihakNameMainDiskonIdProcessor" => "transaksi_id",
// END OF COMPLETE REPEATED LOGIC
```

### 6.4 Modul Kompensasi Harga: `Transaksi.php` (Formula Baku DataTable)
* **Path:** `application/modules/kompensasiharga/controllers/Transaksi.php` (`viewKlaimSupplierDataAjax`)
* **Logika Perbaikan:**
  Hapus fallback liar ke debet cache yang rentan over-debit. Gunakan formula matematis konsisten:
```php
// START OF COMPLETE REPEATED LOGIC
// 1. Plafon hak diskon riil GRN
$diskonPlafon = ($locker_terbit > 0) ? $locker_terbit : $expected_diskon_registry;

// 2. Akumulasi realisasi kredit klaim sah
$totalKlaim = $locker_diklaim;

// 3. Sisa hak bersih yang sah
$sisaDiskon = max(0, $diskonPlafon - $totalKlaim);
// END OF COMPLETE REPEATED LOGIC
```

### 6.5 Model Komponen: `ComRekeningPembantuPiutangSupplierDetailTransItem.php` (Fail-Fast Validation)
* **Path:** `application/models/Coms/ComRekeningPembantuPiutangSupplierDetailTransItem.php`
* **Logika Perbaikan:**
  Terapkan prinsip *Fail-Fast Validation* (Aturan AGENTS.md 3.8). Periksa kecukupan saldo piutang sebelum membuka transaksi database. Jika saldo piutang sudah 0 atau `extern_id` tidak valid, lemparkan pesan error yang jelas dan batalkan transaksi secara utuh (jangan biarkan jurnal umum terbentuk sendiri tanpa buku pembantu).

---

## 7. Spesifikasi 3 Script CLI Rekonsiliasi Database (Database Patch)

Untuk memperbaiki data historis 2024–2026 pada database produksi tanpa mengganggu operasional kasir, disiapkan 3 script CLI mandiri yang berlokasi di direktori `w:\everest_opname28sep\tools\`:

### 7.1 Script 1: `tools/patch_restore_stock_locker_diskon.php`
* **Target:** 114 GRN yang kehilangan baris fisik locker (seperti Diskon 1 pada `GRN.-1.2496`).
* **Algoritma Kerja:**
  1. Cari seluruh GRN aktif (`jenis = '467'`, `status = 1`, `trash = 0`, `trash_4 = 0`).
  2. Parse blob `transaksi_data_registry` untuk mengambil daftar barang dan tarif diskon seharusnya.
  3. Cek apakah setiap jenis diskon (`produk_id`) sudah memiliki baris aktif di `stock_locker_diskon`.
  4. Jika belum ada dan haknya $> 0$:
     * Lakukan `INSERT` ke `stock_locker_diskon` dengan:
       - `transaksi_id` = ID GRN
       - `nomer` = Nomer GRN
       - `produk_id` = ID jenis diskon
       - `nilai` = Nilai hak proporsional barang yang diterima
       - `nilai_diklaim` = 0
       - `state` = `'active'`, `jumlah` = 1, `jenis_locker` = `'stock'`, `jenis` = `'diskon'`
  5. Catat log restorasi ke tabel audit `_audit_patch_diskon_log`.

### 7.2 Script 2: `tools/patch_normalize_grn_overdebit.php`
* **Target:** 2.308 GRN yang mengalami over-debit pada tabel cache buku pembantu piutang.
* **Algoritma Kerja:**
  1. Ambil seluruh transaksi GRN yang memiliki selisih debet cache $> 1.2 \times$ dari fisik locker terbit.
  2. Hitung nilai plafon hak debet yang benar:
     $$\text{Debet Benar} = \text{Total Terbit Fisik Locker}$$
  3. Update baris cache di `_rek_pembantu_subpiutangsuppliertrans_cache`:
     ```sql
     UPDATE _rek_pembantu_subpiutangsuppliertrans_cache
     SET debet = $debetBenar,
         saldo_debet = $debetBenar,
         debet_akhir = $debetBenar - kredit
     WHERE extern_id = $grnId AND rekening = '1010020030';
     ```
  4. Lakukan update selaras pada tabel mutasi `__rek_pembantu_subpiutangsuppliertrans__1010020030` untuk baris debet awal transaksi GRN tersebut.

### 7.3 Script 3: `tools/patch_reconcile_49_klaim_mutasi.php`
* **Target:** 49 transaksi klaim `3333` yang memotong locker tapi tidak memposting kredit pembantu.
* **Algoritma Kerja:**
  1. Loop seluruh 49 dokumen klaim target.
  2. Dapatkan ID GRN referensi yang sah dari tabel `transaksi` (`reference_id` atau `reference_nomer`).
  3. Lakukan verifikasi saldo:
     * **Klaim Sah (Belum Tercatat Kredit):** Buat entri mutasi kredit baru di `__rek_pembantu_subpiutangsuppliertrans__1010020030`:
       - `extern_id` = ID GRN yang benar
       - `debet` = 0
       - `kredit` = Nilai klaim (`transaksi_nilai`)
       - `dtime` = Waktu transaksi klaim asli
       - `keterangan` = `'[PATCH REKONSILIASI] Klaim Diskon ' . $klaimNomer`
     * Update akumulasi `kredit` pada tabel cache agar mencerminkan pelunasan.
     * **Klaim Phantom / Duplikasi (Saldo GRN Sudah 0 Sebelumnya):** Beri penandaan khusus dan buatkan rekomendasi jurnal koreksi pembalik untuk disetujui tim Akuntansi.

---

## 8. Protokol Keselamatan Eksekusi & Urutan Langkah Kerja (Runbook)

Seluruh tindakan perbaikan wajib mengikuti protokol kepatuhan sistem (`AGENTS.md`):

### 8.1 Standar Sintaks PHP 5.6 Mutlak
* ❌ **DILARANG:** Shorthand array `[]`, Null Coalescing `??`, Arrow function `fn()=>`, Spread operator `...`.
* ✅ **WAJIB:** `array()`, `isset($x) ? $x : $default`, `function($x) { return ...; }`.

### 8.2 Protokol Pencadangan Tabel Bayangan (Shadow Table Backup)
Sebelum mengeksekusi script database patch pada database live `192.168.5.10`, jalankan perintah pembuatan tabel cadangan:
```sql
-- EKSEKUSI DI MARIADB SEBELUM PATCHING
CREATE TABLE _bak_20260930_stock_locker_diskon AS SELECT * FROM stock_locker_diskon;
CREATE TABLE _bak_20260930_rek_cache AS SELECT * FROM _rek_pembantu_subpiutangsuppliertrans_cache;
CREATE TABLE _bak_20260930_rek_mutasi AS SELECT * FROM __rek_pembantu_subpiutangsuppliertrans__1010020030;
```

### 8.3 Urutan Eksekusi (Phase Execution Sequence)
```
┌────────────────────────────────────────────────────────┐
│ FASE 1: Penerapan Patch Codebase (Pencegahan Mutlak)   │
│ - Terapkan perbaikan coTransaksiUi.php                 │
│ - Terapkan perbaikan PreSyncDiskonPembelian.php        │
│ - Terapkan perbaikan coTransaksiCore.php               │
│ - Terapkan formula baru Transaksi.php kompensasiharga  │
└───────────────────────────┬────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────┐
│ FASE 2: Simulasi Dry-Run Script CLI (Zero Risk)        │
│ - php tools/patch_normalize_grn_overdebit.php --dry-run│
│ - php tools/patch_restore_stock_locker_diskon.php --dry│
│ - Verifikasi output log dan jumlah baris terdampak     │
└───────────────────────────┬────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────┐
│ FASE 3: Eksekusi Patch Database Produksi (Off-Peak)    │
│ - Buat tabel shadow backup                             │
│ - Jalankan script dengan flag --commit                 │
│ - Verifikasi integritas relasi foreign key/koneksi     │
└───────────────────────────┬────────────────────────────┘
                            │
                            ▼
┌────────────────────────────────────────────────────────┐
│ FASE 4: Validasi & Audit Akhir                         │
│ - Buka dashboard Tool/traceDiskonPembelian             │
│ - Pastikan jumlah GRN Anomali turun mendekati 0        │
│ - Uji buat transaksi PO -> GRN -> Klaim baru di UAT    │
└────────────────────────────────────────────────────────┘
```

---

## 9. Matriks Verifikasi & Troubleshooting Lapangan

Gunakan tabel matriks ini untuk memverifikasi apakah suatu transaksi telah berhasil diperbaiki atau masih menyisakan anomali:

| Parameter Uji | Sebelum Perbaikan (Status Rusak) | Target Setelah Perbaikan (Status Sehat) | Cara Verifikasi di Layar `Tool::traceDiskonPembelian` |
| :--- | :--- | :--- | :--- |
| **Kasus GRN 2496 (Diskon 1)** | Diskon 1 hilang di locker; Layar menampilkan sisa Rp 20,1 Jt. | Diskon 1 terbit Rp 7.633.256; Sisa tampil tepat Rp 7.633.256. | Cek Bagian 1 & Pilar 2: Tampil 3 baris diskon lengkap dengan badge hijau. |
| **Penerimaan PO Parsial** | GRN pertama mendebet utuh Rp 24,6 Jt (total seluruh PO). | GRN pertama hanya mendebet Rp 12,07 Jt (proporsional 50 unit). | Cek Pilar 3 Debet Akuntansi: Sama persis dengan nilai fisik Pilar 2. |
| **Input Klaim Baru (3333)** | Mengirim `pihakMainID = 2`; Mutasi kredit buku pembantu tidak muncul. | Mengirim `pihakMainID = [ID GRN]`; Mutasi kredit terposting seketika. | Cek Bagian 3 Riwayat Pemotong: Badge hijau `"🟢 Kredit Sukses Terposting"`. |
| **GRN Batal / Retur (9911)** | Saldo buku pembantu 0, tetapi saldo locker masih aktif menggantung. | Saldo locker dinolkan via pembalik kontra (`jumlah = -1`). | Cek Status Banner: `"🟢 DISKON & JURNAL SESUAI LENGKAP"` atau badge kontra retur. |

---
**Dokumen ini adalah acuan resmi arsitektur Everest ERP untuk penanganan diskon pembelian.** Setiap perubahan di masa mendatang yang menyentuh tabel `stock_locker_diskon`, `stock_locker_pre_diskon`, atau rekening `1010020030` WAJIB mengacu pada blueprint ini.
