# Sintesis: Dashboard BI, Laporan Penjualan Komprehensif & Pivot Sales

## Dokumentasi teknis terpadu dari 14 blueprint, dev-jurnal, dan laporan forensik ERP SAN (PT Mayagrahakencana) — basis implementasi `san_13mar`, `run_san_report`, dan modul `bi_reports`

**Ruang lingkup sintesis:** 14 dokumen sumber (2.578 baris) yang mencakup blueprint arsitektur pelaporan, jurnal pengembangan BI 10-mode, Human Participation Analytics, laporan pivot aktivitas personil, standardisasi key variant, forensik duplikasi data cache, rekonsiliasi P&L tahunan-vs-bulanan, serta baseline Executive Dashboard.

---

## 2. Ringkasan Eksekutif

Empatteen dokumen ini reducida menjadi **satu tema tunggal: bagaimana mengubah logika ERP yang berorientasi transaksi menjadi read-model analitik yang dapat dipercaya.** Sepuluh dokumen pertama menyelesaikan sisi *arsitektur* (read-model SQL, pivot dinamis, mode analisis, ETL), sedangkan empat dokumen terakhir menyelesaikan sisi *kebenaran angka* (duplikasi cache, mismatch COA, penimpaan data salesman, anomali outstanding Rp 948 miliar).

Temuan paling material:

1. **Sumber data BI berpindah dua kali dalam 10 hari.** `vw_bi_sales_monthly` (read dari tabel cache `rugilaba`, hanya terupdate saat *Monthly Closing*) digantikan `vw_consolidate_monthly` (read langsung dari tabel `jurnal`), karena bulan berjalan selalu tampil Rp 0. Konsekuensinya, target rekonsiliasi `getSalesBranchBreakdown()` yang semula diselaraskan "100%" ke `vw_bi_sales_monthly` menjadi tidak otomatis sinkron lagi.
2. **Klasifikasi akun berlapis berbasis prefiks numerik adalah sumber galat terbesar.** `rekening LIKE '6%'` mengabaikan Rp 9,2 miliar beban dan Rp 2 miliar pendapatan non-operasional pada data 2022 yang masih memakai alias teks (`biaya gaji`, `beban lain lain`), sehingga Laba BersihDashboard BIтельства gelembung Rp 11 miliar dibanding Rugi Laba Tahunan.
3. **Duplikasi cache `run_san_report.penjualan_order` membuktikan bahwa "cache tanpa constraint" gagal secara struktural.** Kombinasi lima cacat — `seller_id` dikomentari keluar dari `$arrHeaders`, guard update 1 jam (`$selisih > 3600`), tanpa `UNIQUE KEY (dtime, seller_id)`, dan akumulasi `+=` buta saat baca — menghasilkan LAST OUTSTANDING Rp 948.137.391.377 (10,7× total order YTD).
4. **Penimpaan nilai karena operator `=` alih-alih `+=`** di `DataOutstanding::callPerSeller()` menghapus nilai transaksi per-salesman secara diam-diam (Adam April 2026: Rp 12.070.792 tercatat vs Rp 30.307.414 sebenarnya; Janson: Rp 140 juta vs Rp 522 juta).
5. **Pivot aktivitas personil hanya mungkin karena parsing serialisasi `print_r` PHP**, dengan tiga bug korosi yang sudah diidentifikasi: regex greedy double-count, `@date_group` di luar proyeksi SELECT, dan variabel `@filter_clause` di WHERE MariaDB.
6. **Executive Dashboard sudah 79/79 report code aktif dengan baseline 12 PASS / 0 FAIL, tetapi 5 dari 6 check H2 bernilai nol** sehingga bukti rekonsiliasinya lemah; H3–H6 masih terbuka.
7. **Blueprint masih kedaluwarsa di tiga titik** (`blue-print-laporan-person.md` masih memuat regex greedy dan `@date_group` yang sudah dikoreksi di jurnal).

Pola rekayasa yang berulang di seluruh dokumen: **guardrail numerik di lapisan presentation, bukan di lapisan data.** thrice: proteksi 3-Way-Matching di `Graph.php`, konfirmasi COA di `app.js`, dedup `MAX()` di view. Semua berhasil, tetapi tidak satu pun memperbaiki sumbernya; rekomendasi jangka panjang (aktifkan `seller_id`, `ADD UNIQUE KEY uq_dtime_seller`) masih berstatus rekomendasi.

---

## 3. Arsitektur Laporan & BI (Read-Model, DDD/CQRS)

> **Catatan atribusi:** tidak ada dokumen sumber yang secara literal memakai istilah "DDD" atau "CQRS". Bagian ini adalah sintesis pola yang teramati pada kode dan skema nyata; setiap klaim ditautkan ke file konkret.

### 3.1 Pemisahan Write-Model dan Read-Model

| Lapisan | Objek konkret | Peran |
| :--- | :--- | :--- |
| **Write-Model (sumber kebenaran transaksi)** | `transaksi`, `transaksi_data`, `transaksi_payment_source`, `transaksi_due_date`, `jurnal`, `stock_locker`, `stock_locker_variant`, `per_employee`, `per_customers` | Sumber otoritatif; dijaga integritas oleh alur modul (`Create`, `FollowUp`, `Printing`, `doFollowup()`) |
| **Read-Model ledger** | `_rek_master_cache`, `_rek_pembantu_penjualan_cache`, `ComRekening::fetchAllBalances_raw()` | Saldo akun untuk KPI/insight; `rek_cache` + flag `acc_coa` |
| **Read-Model agregat** | `heTransaksi_ui` | Tabel domain mapping `sales`, `purchasing`, `expenses` yang dipakai `ExecutiveDashboardModel` |
| **Read-Materialized cache** | `z_sales_salesman_cache` (header, 18.525 baris / 8.111 `transaksi_id`), `z_sales_pembantu_cache` (detail item, 69.015 baris), `rugilaba` (cache bulanan), `run_san_report.penjualan_order` (snapshot per salesman per tanggal) | Disinkronkan per *Monthly Closing* atau per sinkronisasi manual — sumber latency dashboard |
| **Read-Model derivasi (view)** | `vw_bi_sales_monthly`, `vw_consolidate_monthly`, `vw_sales_header_clean`, `vw_sales_item_clean`, `vw_activity_detail`, `vw_activity_daily`, `vw_activity_monthly` | Deduplikasi + klasifikasi akun + agregasi periode; tempat seluruh "business rule" numerik dipusatkan |
| **Read-Model pivot (SP)** | `SP_GET_PIVOT_ACTIVITY` | Pivot kolom dinamis per `step_code` via `GROUP_CONCAT` + `PREPARE`/`EXECUTE` |
| **Read-Model ETL** | `trx_activity_raw`, `trx_human_activity_timeline`, `trx_human_pivot_cache` | Materialisasi hasil parsing field non-relasional |

### 3.2 Pola Arsitektur yang Teramati

**a) View sebagai anti-corruption layer.** `vw_bi_sales_monthly` menerjemahkan dua konvensi penamaan ke satu skema kanonik: `penjualan` → `rekening IN ('penjualan','penjualan projek','4010')` dan `hpp` → `rekening IN ('hpp','hpp projek','5010')`. Semua konsumen (model, controller, hover breakdown) membaca skema kanonik, sehingga konvensi legacy tersembunyi di satu tempat.

**b) Controller tipis, model septrum, view presentasional.** Pola `Bi.php` (router `?mode=` + helper `_branchCell()`/`_cumBranchCell()`), `MdlRugilaba.php` (`getSalesMonthly()`, `getSalesAllMonthly()`, `getSalesDistinctYears()`, `getSalesBranchBreakdown()`), `bi_graph_sales.php` (Highcharts + DataTable) konsisten dipakai di seluruh workstream BI.

**c) Endpoint JSON terpisah dari view.** `eusvc/LaporanApi.php` (`activity_pivot()`, `run_etl()`) dan `eusvc/HumanApi.php` (`get_list`, `module_details`, `timeline`, `run_etl`) melayani SPA `bi_reports/index.html`, bukan controller page-render klasik. Ini decoupling UI–query yang memungkinkan pivot dinamis tanpa reload halaman.

**d) Memory-safe ETL.** `Human_model::run_human_etl()` memproses chunk 5.000 baris untuk menahan memori di bawah 512 MB pada PHP 5.6 dengan 398.126 transaksi —的理由 langsung dari naive real-time join ke `transaksi_data`, `per_employee`, `per_customers` yang有条 memicu *Memory Exhaustion*.

**e) Ketidakkonsistenan read-model yang belum diselesaikan.** `z_sales_salesman_cache` bersifat *ledger event* (satu `transaksi_id` muncul pada banyak `rekening` sesuai lifecycle), sedangkan `rugilaba`/`jurnal` bersifat *snapshot saldo*. Menggabungkan keduanya tanpa kontrak eksplisit menghasilkan definisi "omset" yang berbeda antar-dashboard (lihat §11 Kontradiksi-1).

---

## 4. Dashboard Penjualan Komprehensif (ISO & IFRS)

### 4.1 Empat Modul Laporan dan Pemetaan Standar

| Modul | Dasar Standar | Prasyarat Kepatuhan | Implementasi |
| :--- | :--- | :--- | :--- |
| **M1 Finansial Eksternal** | IFRS 15 / PSAK 72 | Pendapatan diakui saat kontrol beralih | Filter `rekening IN ('582spd','382spd')`, agregasi `harga_netto` sebagai `Net_Sales_Revenue` |
| **M2 Operasional SLA** | ISO 9001:2015 K.8.2 | Pemenuhan janji layanan & kapasitas transaksi | `DATEDIFF(dtime_kirim, dtime_order)` dan `DATEDIFF(dtime_terima, dtime_kirim)` |
| **M3 Kinerja Sales & RFM** | ISO 9001:2015 K.9.1.3 | Efektivitas sistem manajemen via metrik bisnis | Pivot per salesman per tahun + segmentasi RFM |
| **M4 Analisis Produk** | Standar Akuntansi Biaya / Subsidiary Ledger | Profitabilitas produk & bundling | `z_sales_pembantu_cache` untuk `produk_part` |

### 4.2 View Deduplikasi (Kunci Akurasi)

`vw_sales_header_clean` menerapkan `MAX()` pada enam kolom nilai (`harga_bruto`, `diskon_nilai`, `ppn_nilai`, `harga_netto`, `ongkir_nilai`, `harga_nppn`) dengan `GROUP BY` 14 kolom identitas (`transaksi_id`, `thn`, `bln`, `tgl`, `fulldate`, `seller_id`, `seller_nama`, `customer_id`, `customer_nama`, `rekening`, `jenis`, `dtime_order`, `dtime_kirim`, `dtime_terima`). Tanpa ini, `SUM(harga_netto)`直线 menginflasi hasil karena tabel memiliki *literal duplicate rows* (baris identik kolom, waktu, status, dan nilai).

`vw_sales_item_clean` memakai strategi berbeda — `SUM(qty_kredit) AS total_qty_terjual` dengan `MAX(harga_netto) AS harga_netto_item` — karena pada level item duplikasi adalah pengulangan baris produk nyata, bukan duplikasi header.

### 4.3 Formula Kunci

```
Conversion_Rate = COUNT(DISTINCT CASE WHEN rekening IN ('582so','382so') THEN transaksi_id END)
               / COUNT(DISTINCT CASE WHEN rekening IN ('582spo','382spo') THEN transaksi_id END) * 100

Avg_Days_Order_to_Ship    = ROUND(AVG(DATEDIFF(dtime_kirim, dtime_order)), 1)
Avg_Days_Ship_to_Received = ROUND(AVG(DATEDIFF(dtime_terima, dtime_kirim)), 1)

Gross_Sales     = SUM(harga_bruto)
Net_Sales       = SUM(harga_netto)      -- harga_bruto - diskon_nilai
PPN_Keluaran    = SUM(ppn_nilai)
Total_Billing   = SUM(harga_nppn)       -- harga_netto + ppn_nilai
```

Segmentasi RFM M3: `Recency <= 180` hari **dan** `Frequency >= 10` → `Loyal Customer`; `Recency > 180` **dan** `Frequency >= 10` → `At Risk (Passive)`; `Recency > 360` → `Churned (Lost)`; sisanya `Regular Customer`.

### 4.4 UI/UX

Filter panel dinamis: datepicker rentang + preset (Bulan ini/Tahun ini/YTD), multiselect salesman, autocomplete customer, checkbox tipe penjualan (Lokal/Proyek/Ekspor), **toggle Mode Standar (Eksternal/IFRS vs Internal/ISO)**. KPI cards di baris atas, grafik tren Highcharts (garis ganda Omset Bersih vs Nilai Diskon), tabel pivot DataTables dengan sorting instan, export **Excel/PDF/Print**, dan pagination dinamis untuk puluhan ribu baris.

### 4.5 Makna Kode `rekening` pada `z_sales_*`

| Tahap | Kode | Peran |
| :--- | :--- | :--- |
| Pre-Order (SPO) | `582spo` (lokal), `382spo` (internasional), `588spo` (proyek) | Pipeline entry |
| Confirmed Order (SO) | `582so`, `382so`, `588so` | Basis realisasi omset |
| Shipped/Delivered (SPD) | `582spd`, `382spd` | Kontrol beralih → IFRS 15 |
| Project Receipt | `7499` | Penerimaan proyek |

Kolom turunan pada `z_sales_salesman_cache`: `harga_netto = harga_bruto - diskon_nilai`, `harga_nppn = harga_netto + ppn_nilai`.

---

## 5. Upgrade BI Graph Sales ke 10-Mode Analytics

### 5.1 Rantai Komponen

```
[DB] rugilaba → [View] vw_bi_sales_monthly → [Model] MdlRugilaba → [Controller] Bi.php → [View] bi_graph_sales.php
```

**Masalah awal:** `viewGraphSales()` menampilkan grafik bulanan statis tanpa mode analisis dan tanpa visualisasi tabel. Akar nilai nol: data `rugilaba` tahun 2023–2026 memakai kode numerik (`4010`/`5010`/`6010`), bukan nama rekening literal, sehingga query pencarian nama gagal.

### 5.2 Skema `vw_bi_sales_monthly`

| Kolom output | Ekspresi |
| :--- | :--- |
| `year`, `month` | `thn`, `bln` |
| `periode` | `CONCAT(thn,'-',LPAD(bln,2,'0'))` |
| `penjualan` | `SUM(CASE WHEN rekening IN ('penjualan','penjualan projek','4010') THEN kredit-debet ELSE 0 END)` |
| `return_penjualan` | `rekening = 'return penjualan'` |
| `jasa_kirim` | `rekening = 'jasa kirim'` |
| `laba_lain_lain` | `rekening = 'laba lain lain'` |
| `keuntungan_kurs` | `rekening = 'keuntungan kurs'` |
| `hpp` | `rekening IN ('hpp','hpp projek','5010')` |
| `total_biaya` | `kategori='biaya' AND rekening NOT IN ('hpp','hpp projek','5010','return penjualan')` |
| `penjualan_net` | `penjualan_net = penjualan + return_penjualan + jasa_kirim` (derived) |
| `total_hpp` | duplikat dari `hpp` (derived) |

`WHERE periode='bulanan' AND status=1 AND trash=0 AND thn>0 AND bln>0 GROUP BY thn, bln`.

### 5.3 Definisi 10 Mode (dihitung di `Bi.php`)

1. **Monthly P&L** — `Laba Kotor = Penjualan Netto + HPP`; `Laba Bersih = Laba Kotor + Total Biaya` (HPP & biaya bernilai negatif sehingga penjumlahan tetap benar).
2. **Yearly P&L** — agregasi total tahunan dari data bulanan.
3. **YTD** — akumulasi Januari s.d. bulan berjalan pada tahun terpilih.
4. **QTD** — akumulasi per kuartal (Q1–Q4) s.d. bulan berjalan.
5. **Rolling 12 Months (R12)** — 12 bulan terakhir dinamis, tanpa batas tahun kalender.
6. **MoM Growth** — perbandingan penjualan & laba bersih bulan berjalan vs bulan sebelumnya.
7. **YoY Growth** — bulan berjalan vs bulan sama tahun sebelumnya.
8. **Gross & Net Margin** — rasio terhadap Penjualan Netto.
9. **Budget vs Actual** — target bulanan (menunggu integrasi tabel budget).
10. **Running Cumulative Total** — tren kumulatif total bulan ke bulan.

Navigasi: parameter GET `?mode=...` dirutekan ke 10 method analisis pada `Bi.php`; tab 10-mode + filter tahun/bulan dinamis di `bi_graph_sales.php`; Highcharts column + spline dengan tooltip Rupiah (K/M/B); DataTable dengan ekspor Excel dan cetak PDF.

### 5.4 Model yang Ditambahkan

`MdlRugilaba.php`: `getSalesMonthly()`, `getSalesAllMonthly()`, `getSalesDistinctYears()`. Ketiganya **kemudian dialihkan** dari `vw_bi_sales_monthly` ke `vw_consolidate_monthly` (§6.4).

### 5.5 UI Hybrid Hover Kontribusi Cabang (2026-07-06)

Kolom tabel dipulihkan ke **12 kolom lengkap**: Net Penjualan, HPP, Efisiensi, Laba Kotor, Total Biaya, Selisih Kurs, Selisih Opname, Jasa Kirim, Lain-Lain Net, Laba Bersih. `MdlRugilaba::getSalesBranchBreakdown()` diperluas mengembalikan array asosiatif seluruh metrik P&L **per cabang × per tahun × per bulan**. Dua helper privat di `Bi.php`:

- `_branchCell()` — memformat sel, mencari kontribusi cabang, mengurutkan dari terbesar, menyematkan detail ke atribut `title` HTML.
- `_cumBranchCell()` — varian untuk sel kumulatif mode YTD/QTD/R12.

P&L multiplier diterapkan: faktor `-1` untuk HPP dan Total Biaya agar tanda hover sejajar dengan nilai kolom. Untuk grafik, data series `Laba Bersih` dikirim beserta array `branches`, dibaca `tooltip.formatter` di view.

### 5.6 Perbaikan Double-Counting 7,6 Miliar (2026-07-07)

Tiga penyebab pada `getSalesBranchBreakdown()`:

1. Akun `rekening LIKE '6%'` masuk **ganda** ke `total_biaya` **dan** `lain_lain_netto` karena query `lain_lain_netto` tidak mengecualikan `6%` saat memfilter `kategori='biaya'`.
2. Akun non-operasional `7020020` (selisih opname) masuk `total_biaya` karena bertipe `kategori='biaya'`.
3. `penjualan_net` masih menyertakan `'jasa kirim'` padahal sudah dihitung terpisah di `jasa_kirim`.

Perbaikan: `total_biaya` dibatasi `rekening LIKE '6%'` (+ penanganan `kategori='biaya'` untuk data lama) dengan pengecualian rekening non-operasional; `lain_lain_netto` difilter ke rentang `7%`, `8%`, `9%`, `3%` minus rekening yang sudah masuk metrik khusus; `penjualan_net` dibersihkan dari `'jasa kirim'`. **Hasil: selisih 7.6 miliar → 0.00 untuk semua periode.**

---

## 6. Human Participation Analytics

### 6.1 Masalah dan Arsitektur 2-Tier

`transaksi` memiliki 390.000+ baris. Join real-time ke `transaksi_data`, `per_employee`, `per_customers` saat page load memicu *Memory Exhaustion* pada PHP 5.6. Solusi:

- **Tier 1 — `trx_human_activity_timeline`:** log flat keterlibatan per transaksi, dengan nama produk di-*pre-aggregate* saat ETL agar tidak perlu query detail terpisah.
- **Tier 2 — `trx_human_pivot_cache`:** hitungan akumulasi keterlibatan per modul operasional (Sales, Purchase, Finance, Logistics, Production).
- **Chunked ETL 5.000 baris** untuk menjaga memori di bawah limit server 512 MB.

### 6.2 Berkas

| Aksi | Path | Peran |
| :--- | :--- | :--- |
| NEW | `scratch/setup_human_db.php` | Skema 2 tabel |
| NEW | `application/models/Human_model.php` | Chunked ETL + query detail peran modul + timeline filterable |
| NEW | `application/controllers/eusvc/HumanApi.php` | Endpoint `get_list`, `module_details`, `timeline`, `run_etl` |
| MODIFY | `bi_reports/index.html` | Pivot table utama, modal per modul, slide-over timeline drawer |
| MODIFY | `bi_reports/assets/style.css` | `backdrop-filter` modal, slide-over drawer, timeline badges |
| MODIFY | `bi_reports/assets/app.js` | Fetch, DataTable init, Highcharts donut, drawer filters |

### 6.3 Kinerja Terukur (DB `san_13mar`, 398.126 transaksi operasional)

| Endpoint | Waktu respon | Catatan |
| :--- | --- | :--- |
| ETL `run_etl` | ~20 detik | 590.681 baris keterlibatan, tanpa memory leak |
| `get_list` | **1,21 ms** | Memakai cache data |
| `module_details` | **248 ms** | Personil teraktif Wilson — 62k transaksi |
| `timeline` | **150 ms** | 5 transaksi per halaman render |

Alur UI 3 layar: (1) pivot orang × hitungan keterlibatan per modul; (2) klik jumlah modul → modal donut persentase peran (contoh: Wilson di Finance `actor` = 100%); (3) klik nama personil → slide-over kanan berisi riwayat kronologis, list produk tertabel, nominal, dan filter per modul/tanggal.

### 6.4 Tabel Validasi Harga Produk (2026-07-06)

Masalah: `GROUP_CONCAT` produk menghasilkan teks satu baris panjang; tidak ada rincian harga untuk cross-check akumulasi vs total transaksi.

- **ETL** (`run_human_etl()`, `sync_incremental()`) menyisipkan harga satuan dan total: format `Nama Produk (Qty Satuan @ HargaSatuan = TotalNilai)`.
- **Frontend** `formatProductSummaryTable()` memakai regex `/(.+?)\s*\(([\d\.,]+)\s*([^@)]*)(?:\s*@\s*([\d\.,]+)\s*=\s*([\d\.,]+))?\)/g` untuk mengekstrak Nama, Qty, Satuan, Harga Satuan, Total.
- **Tabel validasi 4 kolom** (`Nama Produk`, `Jumlah`, `Harga Satuan`, `Total`) diformat `fmtRp()`; **fallback otomatis** ke tabel 2 kolom bila data harga tidak ada (cache transaksi lama).
- `renderModalTopProducts()` diperbarui agar satuan/unit tidak terpolusi teks harga.

### 6.5 Pivot Aktivitas Personil dari `counters_intext`

Sumber data adalah field `counters_intext` pada tabel `transaksi` — serialisasi output `print_r` PHP. Objek DB di `san_13mar`: `dim_person` (TABLE), `trx_activity_raw` (TABLE, materialized), `vw_activity_detail` (VIEW), `vw_activity_daily` (VIEW), `SP_GET_PIVOT_ACTIVITY` (PROCEDURE).

**Tiga bug yang harus diperbaiki:**

1. **Regex double-count.** Asli `'/\[(.*?)\|olehID\]/s'` bersifat greedy dan mulai mencocokkan dari `[` pertama yang berbeda sub-array (`stepCode|placeID` → `stepCode|olehID`), menyebabkan parsing ganda. Fix: `'/\[([^\]]+)\|olehID\]/'` (non-bracket). Tambahan: ETL hanya memproses block dengan key prefix **tepat** `stepCode`, bukan `stepCode|placeID|olehID`.
2. **SP `@date_group` di luar proyeksi.** Versi pertama meng-`CONCAT` `@date_group` *setelah* `SUM(...)`, sehingga kolom `tahun` tidak dikenali (`Unknown column`). Fix: SP di-refactor agar tiap mode (DAY/MONTH/QUARTER/YEAR) membangun query SELECT sendiri dengan kolom tanggal dideklarasikan langsung di SELECT clause.
3. **Variabel `@filter_clause` di WHERE MariaDB.** `SET @filter_clause = '...'` lalu `WHERE ... AND @filter_clause` tidak dievaluasi. Fix: `@filter_jenis` ber-prefix `AND `, di-embed via `CONCAT` ke `@q_steps` dan `@sql_main`.

**Hasil verifikasi ETL:** 95.162 aktivitas dari 96.915 transaksi, 73 personil unik. Step code penjualan terdeteksi: `582spo`, `582so`, `582pkd`, `582spd`, `582`, `982`, `982r`, `982g`, `382spo`, `382so`, `382pkd`, `382spd`, `382`, `1582spo`. Semua 4 mode SP (DAY/MONTH/QUARTER/YEAR) berfungsi.

---

## 7. Laporan Person, Segment Key & Cache Salesman

### 7.1 Pivot Aktivitas Step × Personil (Blueprint CodeIgniter 3)

**Lingkungan:** MariaDB 10.3, PHP 5.6, CodeIgniter 3.

**`dim_person`:** `person_id INT PRIMARY KEY`, `person_name VARCHAR(255) NOT NULL`, InnoDB, `utf8`.

**`trx_activity_raw`:** `id INT AUTO_INCREMENT PRIMARY KEY`, `trx_id INT`, `person_id INT`, `step_code VARCHAR(50)`, `counter INT DEFAULT 0`, `fulldate DATE`, `jenis_master VARCHAR(24)`, `nomer VARCHAR(120)`, dengan lima index: `idx_trx`, `idx_person`, `idx_step`, `idx_date`, `idx_master`.

**`vw_activity_detail`** memetakan `modul_label`: `jenis_master='582'` → `Penjualan`, `'789'` → `Pembelian`, selain itu `Lainnya`. `vw_activity_daily` mengagregasi `SUM(counter) AS total_aktivitas` per person/step/tanggal; `vw_activity_monthly` menurunkan ke `YEAR(tgl)`/`MONTH(tgl)`.

**`SP_GET_PIVOT_ACTIVITY(p_date_start, p_date_end, p_group_by, p_sort_col, p_sort_dir)`:**

```
SET SESSION group_concat_max_len = 1000000;
SELECT GROUP_CONCAT(DISTINCT CONCAT(
         'SUM(CASE WHEN step_code = ''', step_code, ''' THEN total_aktivitas ELSE 0 END) AS `', step_code, '`')
         ORDER BY step_code ASC)
  INTO @sql_steps FROM vw_activity_daily WHERE tgl BETWEEN p_date_start AND p_date_end;
IF @sql_steps IS NULL THEN SET @sql_steps = '1 AS dummy'; END IF;
```

Query utama dibentuk dengan `CONCAT` atas `@select_cols + @sql_steps + 'SUM(total_aktivitas) AS grand_total'`, `GROUP BY @group_cols`; default `ORDER BY grand_total DESC`. Eksekusi `PREPARE stmt FROM @sql; EXECUTE; DEALLOCATE PREPARE`.

**Regex ETL pada `Activity_model::run_etl()`:**

```
preg_match_all('/\[(.*?)\|olehID\]\s*=>\s*Array\s*\(\s*(.*?)\s*\)\s*\)/s', $text, $blocks, PREG_SET_ORDER);
preg_match_all('/\[([^\]]+)\|([0-9]+)\]\s*=>\s*([0-9]+)/', $inner, $items, PREG_SET_ORDER);
```

`TRUNCATE` kedua tabel sebelum ETL; `counter == 0` di-skip; nama personil fallback `'User_' . $personId` bila tidak cocok dengan `oleh_nama`/`customers_nama`; batch insert via `insert_batch()`; dimensi personil via `INSERT ... ON DUPLICATE KEY UPDATE person_name = VALUES(person_name)`.

**Catatan operasional `get_pivot_data()`:** setelah `CALL`, wajib `$this->db->close(); $this->db->initialize();` untuk menghindari error *Commands out of sync*; alternatif `$this->db->conn_id->next_result()`. Cron harian jam 01:00 (`php /path/to/index.php laporan run_etl`).

**Controller `Laporan.php`:** `index()` (render `laporan_aktivitas`), `ajax_aktivitas()` (validasi `^\d{4}-\d{2}-\d{2}$`, default `date('Y-m-01')`..`date('Y-m-d')`, group default `DAY`), `run_etl()`.

**View `laporan_aktivitas.php`:** `buildColumns()` memisahkan `fixedKeys = ['person_id','person_name','grand_total']` dari kunci dinamis yang di-sort; event `xhr.dt` memicu `table.destroy()` lalu re-init dengan kolom baru — pola re-init (bukan mutasi kolom) yang konsisten dipakai juga di `app.js`.

**Contoh hasil:** `person_id 245 / jkt2` → `582spo 2`, `582so 1`, `582pkd 1`, `582spd 1`, `582 1`, `grand_total 6`.

### 7.2 Standarisasi Segment Key Variant (Format 3 Segmen)

**Masalah:** Penjualan memakai `variant:{product_master_id}:{variant_id}` (3 segmen) sedangkan Pembelian memakai `variant:{variant_id}` (2 segmen) — sehingga stok bisa berbeda. **Collision risk:** `variant_id=7` pada produk A (`id=1759`) dan produk B (`id=1800`) sama-sama menghasilkan key `variant:7` di session. Key ini dipakai sebagai `cart_key` di session items, bukan Redis semata.

**Target:** semua modul memakai `variant:{product_master_id}:{variant_id}`.

**Komponen inti di `MdlMother.php`:**

```
get_variant_cache_key($product_master_id, $variant_id) → "variant:{$product_master_id}:{$variant_id}"

parse_variant_key($key):
  validasi strpos($key,'variant:') === 0
  $parts = explode(':', $key)
  count>=3 && is_numeric($parts[1]) && is_numeric($parts[2])
    → ['produk_id'=>(int)$parts[1], 'variant_id'=>(int)$parts[2]]
  selain itu → ['produk_id'=>0, 'variant_id'=>0]   // format lama TIDAK DIDUKUNG
```

`Variant_model` (tabel `product_variants`) menyediakan `get_key_from_variant_id($variant_id)` yang melakukan lookup `product_master_id` lalu memanggil builder.

**Key construction yang wajib diganti:** `_selectorItem.php:88,111,597`; `_processSelectProduct.php:37,192`; `views/variant_picker.php:132` (satu-satunya manual, karena JavaScript tidak bisa memanggil PHP model → `'variant:' + produkId + ':' + variantId`); `FollowUp.php:142` (`resolveFollowupItemKey()`) dan `:2315`; `Printing.php:73` (`resolvePrintingItemKey()`) dan `:2557`; `Mdls/MdlProdukVarian.php:267`. Lokasi `:2315`, `:73`, `:2557` memerlukan lookup DB `produk_id` (opsi: JOIN `transaksi_data` atau `product_variants`).

**Parsing yang salah dan harus diganti:** `$variantId = (int)str_replace('variant:','',$key)` dan `$parts[1]` (index salah). Lokasi: `_processSelectProduct.php:29,33,155`; `_shoppingCart.php:71` (`$parts[1]` → `$parts[2]`); `FollowUp.php:629-630` (yang dicari sebenarnya `produk_id`). Deteksi sisa: `grep -r "'variant:'" application/ && grep -r '"variant:"' application/`.

**Status Fase 2 (selesai, 18 file):** 12 `Coms` — `ComLockerStock.php:56-57`, `ComLockerStockVariant.php:70,94`, `ComLockerStockParentVariant.php:55,75`, `ComLockerStockMutasi.php:189-190`, `ComLockerStockMutasiVariant.php:84`, `ComFifoProdukJadiVarian.php:88`, `ComPriceProduk.php:214-215`, `ComPriceProdukLastPurchase.php:213-214`, `ComPriceProdukPerSupplier.php:206-207`, `ComRekeningPembantuProduk.php:236,352`, `ComRekeningPembantuProdukRiil.php:212`, `ComRekeningPembantuProdukVarian.php:242`; 3 `Mdls` — `MdlLockerStockVariant.php:186-187`, `MdlLockerStockParentVariant.php:186-187`, `MdlProdukVarian.php:267`; 3 `PreProcs` — `PreLockerStock.php:25,199`, `PreFifoProdukJadiVarian.php:25`, `PreFifoProdukJadi.php:138`. `MdlMother` sudah autoload sehingga tidak perlu `require_once`.

**Sisa pekerjaan (Fase 3–5, per modul `distribusifg`/`opname`/`pembelian`/`pembelianimport`/`pindahgudang`/`requeststok`/`konversi`):** parsing controller, key construction controller, `views/variant_picker.php`, `_shoppingCart.php` JS (konversi sudah 3-seg, verifikasi saja), commit tunggal, deploy staging, 6 skenario uji, dan broadcast maintenance bila ada session aktif.

**Risiko migrasi session:** `$_SESSION[$cCode]['items']` tidak dapat dimigrasi via Redis CLI. Session lama dengan key `variant:7` akan tidak terdeteksi (user harus pilih ulang variant), tetapi transaksi tetap berjalan karena data dibaca dari `transaksi_data`. Non-variant items tidak terpengaruh. Mitigasi: deploy di luar jam kerja. Rollback cukup `git checkout`; session dan Redis terisi otomatis saat runtime.

### 7.3 Tabel `z_sales_salesman_cache` dan Formula Pivot

**Profil:** database `san_13mar`, rentang **2021-01-04 s/d 2026-03-12**, **18.525 baris**, **8.111 `transaksi_id` unik**. Level header/dokumen.

| Kolom | Tipe | Peran pivot |
| :--- | :--- | :--- |
| `transaksi_id` | `int(11)` | Identitas unik + kunci dedup |
| `rekening` / `jenis` | `varchar` | Tahap transaksi (`582so`, `582spd`, ...) |
| `seller_nama`, `customer_nama` | `varchar` | Dimensi baris pivot |
| `thn` / `bln` / `tgl` | `varchar` | Dimensi waktu; perlu CAST bila dibandingkan numerik |
| `fulldate` | `date` | `YYYY-MM-DD`, basis RFM |
| `harga_bruto` | `decimal` | Penjualan kotor |
| `diskon_nilai` | `decimal` | Potongan harga |
| `ppn_nilai` | `decimal` | PPN transaksi |
| `harga_netto` | `decimal` | **Dasar utama omset** = `harga_bruto - diskon_nilai` |
| `ongkir_nilai` | `decimal` | Ongkos kirim |
| `harga_nppn` | `decimal` | Total tagihan = `harga_netto + ppn_nilai` |

`z_sales_pembantu_cache` (subsidiary ledger, **69.015 baris**) menambah `produk_kode`, `produk_part` (mis. `FC-H2`), `produk_satuan`, `produk_jenis`, `kategori_nama` — satu-satunya sumber untuk pivot per produk.

**Empat formula pivot:**

- **(A) Pivot SO per Salesman per Tahun** — sub-query dedup `MAX(harga_netto) AS net_sales` dengan `GROUP BY transaksi_id, thn, seller_nama`, filter `rekening IN ('582so','382so','588so')`; pivot horizontal `FORMAT(SUM(CASE WHEN thn='20XX' THEN net_sales ELSE 0 END), 2, 'id_ID')`.
- **(B) Pipeline SPO vs SO vs SPD vs Project Receipt** — `CASE` memetakan rekening ke label tahap, `GROUP BY transaksi_id, seller_nama, stage`, diurutkan menurut total SO.
- **(C) Konsentrasi Pelanggan** — pivot `customer_nama` × salesman (3 nama top + `Salesman Lainnya`), `LIMIT 15`.
- **(D) Pivot Bulanan per Tahun** — 12 kolom `Jan`…`Des` + `Total`, `GROUP BY thn`.

**Dua pendekatan pivot dinamis (anti-hardcode tahun):**

- **Pendekatan A (sangat direkomendasikan) — flat SQL + PHP.** SQL hanya mengembalikan `(seller_nama, thn, total_sales)`; PHP membangun `$pivot[$salesman][$year]` dan `$years` unik, `sort($years)`, view merender header iteratif dengan `number_format($amount, 2, ',', '.')`. Beban pivot dipindah dari DB ke PHP.
- **Pendekatan B — dynamic SQL.** `SELECT GROUP_CONCAT(DISTINCT CONCAT('FORMAT(SUM(CASE WHEN thn = ''', thn, ''' THEN net_sales ELSE 0 END), 2, ''id_ID'') AS `Sales ', thn, '`')) INTO @sql ...` lalu `PREPARE`/`EXECUTE`/`DEALLOCATE`.

**Analisis lanjutan:** RFM (`Recency = DATEDIFF(CURRENT_DATE(), MAX(fulldate))`, `Frequency = COUNT(DISTINCT transaksi_id)`, `Monetary = SUM(net_sales)`); SLA/lead time (`AVG(DATEDIFF(dtime_kirim, dtime_order))`, `AVG(DATEDIFF(dtime_terima, dtime_kirim))`); Market Basket via self-join `ON p1.transaksi_id = p2.transaksi_id AND p1.produk_part < p2.produk_part` untuk menghindari pasangan ganda; Pareto 80/20; deteksi churn 6 bulan.

---

## 8. Analisis Duplikasi Data & Perbandingan Laporan

### 8.1 Anomali LAST OUTSTANDING Rp 948 Miliar

**Konteks:** 20 September 2026, SAN ERP, workspace `z:\san_11sep`, modul `application/modules/dashboard` & `application/modules/laporan`. Sel-seluruh kartu bersumber dari `application/modules/dashboard/controllers/Graph.php`, method render `viewSummary_2()`, method pengambil data `private function callSummary($cabang_id, $last_tr_id)` (twin-nya `callSummary_now()`).

### 8.2 Pemetaan 7 Kartu Dashboard

| # | Kartu | Nilai | Tabel Sumber | Logika |
| :--: | :--- | --- | :--- | :--- |
| 1 | 🟧 Persediaan Produk | `85,522,583,609` | `_rek_master_cache` | `rekening='1010030030'`, kolom `debet`, `periode='forever'` |
| 2 | 🟪 (A) Prev. Outstanding | `5,977,579,142 YTD` | `run_san_report.penjualan_order` | `MdlReporting::getSaldoBawahOutstanding()`, snapshot `2026-01-31` |
| 3 | 🟥 (B) Order | `7,059,391,735 MTD New` / `88,647,281,873 YTD` | `z_sales_salesman_cache` | `_get_order_mtd_live()` & `_get_order_ytd_live()`, rekening `582so`/`588so`/`382so`, formula **Order − Reject − Closed** |
| 4 | 🟪 (C) Total Pengiriman | `430,020,042 MTD New` / `1,106,357,864 MTD All` / `65,398,620,060 YTD` | `penjualan_order` & `_rek_pembantu_penjualan_cache` | MTD New dari `now_saldo_kirim_all_new`; MTD All & YTD dari buku besar pembantu |
| 5 | 🟦 (D) Last Outstanding | `6,629,371,693 MTD New` / **`948,137,391,377 All`** ⚠️ | `run_san_report.penjualan_order` | MTD = Order MTD − Kirim MTD New; All = `SUM(last_kredit_all)` snapshot akhir bulan |
| 6 | 🟦 Best Order of the Month | `11,635,166,158` | Live DB via `DataOutstanding` | `DataOutstanding::callPerSeller()`, Agustus 2026 |
| 7 | 🟦 Best Sales of the Month | `6,331,164,958` | Live DB via `DataOutstanding` | `callPerSeller()`, Agustus 2026 |

### 8.3 Rekonsiliasi Matematis (Siklus Order-to-Delivery)

```
(A) Prev Outstanding        =  5.977.579.142
(B) Order Masuk YTD         = 88.647.281.873
(A+B) Total Beban Order     = 94.624.861.015
(C) Realisasi Pengiriman    = 65.398.620.060
(D) Sisa Outstanding Riil   = 29.226.240.955
```

Nilai `948.137.391.377` = **10,7 kali** total seluruh order perusahaan setahun — definitif duplikasi data, bukan nilai bisnis.

### 8.4 Forensik: Lima Rantai Masalah

1. **`seller_id` dikomentari di header sinkronisasi** — `application/modules/laporan/controllers/Outstanding.php:3124`, blok `"seller_id" => array("label" => "sID")` tidak aktif, sehingga `$arrHeaders` tidak memuat kolom itu ketika `$rp->setFields($arrHeaders)` dijalankan.
2. **Identitas seller hilang saat penulisan** — `MdlReporting::writePenjualanOrder()` (`MdlReporting.php:864-895`): `$kolomDatas = array_keys($fields)` hanya mengambil kolom yang ada di `$arrHeaders`; kolom yang tidak ada diisi `0`. Akibatnya `$seller_id = $newdatas['seller_id']` selalu NULL/0 dan `lookupData($tbl, array("dtime"=>$tgl, "seller_id"=>$seller_id))` tidak dapat membedakan record antar-salesman.
3. **Update terkunci jeda 1 jam** — `MdlReporting.php:924-936`: `if(($last_update_bln == $bln_now) && ($selisih > 3600))`. Karena sinkronisasi berjalan dalam satu loop (detik yang sama, `$selisih = 0`), pengecekan dilewati dan proses melakukan `INSERT` terus-menerus.
4. **Tidak ada `UNIQUE KEY (dtime, seller_id)`** pada `run_san_report.penjualan_order`.
5. **Akumulasi buta saat pembacaan** — `MdlReporting.php:1047-1055`: `$sumKey[$sumKolom] += $cekdata->$sumKolom;` menjumlahkan seluruh baris duplikat tanpa `DISTINCT`/`GROUP BY`.

### 8.5 Solusi: Proteksi 3-Way-Matching & Guardrail

Dipasang di `Graph.php` pada `callSummary()` dan `callSummary_now()`:

```php
$kirim_ytd_actual = (isset($src_datas->kredit_ytd_cache) && $src_datas->kredit_ytd_cache > 0)
    ? ($src_datas->kredit_ytd_cache * 1) : ($src_datas->kredit_ytd * 1);
$total_order_obligation = ($prev_outstanding_awal * 1) + ($src_datas->netto_ytd * 1);
$outstanding_3way = max(0, $total_order_obligation - $kirim_ytd_actual);
$raw_outstanding  = isset($src_datas->outstanding) ? ($src_datas->outstanding * 1) : 0;

$is_anomaly = ($raw_outstanding > $total_order_obligation)
           || ($raw_outstanding < 0)
           || ($total_order_obligation > 0
               && abs($raw_outstanding - $outstanding_3way) > (0.5 * $total_order_obligation));

if ($is_anomaly) {
    $final_outstanding_ytd = $outstanding_3way;
    log_message('error', "3-Way-Matching: Anomali Last Outstanding terdeteksi ...");
} else {
    $final_outstanding_ytd = $raw_outstanding;
}
$penjualan['outstanding_ytd']["kredit"] = $final_outstanding_ytd;
$penjualan['outstanding']["kredit"]     = $final_outstanding_ytd;
```

Proteksi lanjutan: Kirim MTD New dibatasi agar tak melebihi `Order MTD`/`Kirim MTD All`; Outstanding MTD New dihitung `max(0, Order MTD − Kirim MTD New)`; Saldo Awal dibatasi tidak melebihi total order tahunan wajar.

**SQL diagnosis & pembersihan:**

```sql
SELECT dtime, COUNT(*), SUM(last_kredit_all), SUM(now_saldo_order_all), SUM(now_saldo_kirim_all)
FROM run_san_report.penjualan_order WHERE dtime = '2026-09-30' GROUP BY dtime;

SELECT dtime, seller_id, COUNT(*) AS jumlah_duplikat, SUM(last_kredit_all)
FROM run_san_report.penjualan_order
WHERE dtime >= '2026-01-01' GROUP BY dtime, seller_id
HAVING COUNT(*) > 1 ORDER BY jumlah_duplikat DESC;

CREATE TABLE run_san_report.penjualan_order_bak_sep2026 AS
  SELECT * FROM run_san_report.penjualan_order WHERE dtime = '2026-09-30';
DELETE FROM run_san_report.penjualan_order WHERE dtime = '2026-09-30';
```

**Rekomendasi jangka panjang:** aktifkan kembali blok `seller_id` di `Outstanding.php:3124`; `ALTER TABLE run_san_report.penjualan_order ADD UNIQUE KEY uq_dtime_seller (dtime, seller_id);`

### 8.6 Rekonsiliasi P&L Tahunan vs Bulanan (Data 2022)

**Gejala:** Laba Bersih Dashboard BI (sumber bulanan) **Rp 11 miliar lebih besar** dari Rugi Laba Tahunan; memicu kebingungan admin soal selisih Audit 3-Way Match.

**Akar:** filter akun hardcode `rekening LIKE '6%'` dan `LIKE '7%'` pada `vw_bi_sales_monthly` dan `vw_consolidate_monthly`. Rekapitulasi bulanan 2022 tidak menyimpan nomor akun resmi, melainkan alias teks (`biaya usaha`, `biaya gaji`, `beban lain lain`) yang tidak berawalan 6/7. Akibatnya **Rp 9,2 miliar beban** dan **Rp 2 miliar pendapatan lain** dianggap 0.

**Perbaikan (bulletproof patch):** ganti hardcode prefiks dengan *auto-catch* `kategori = 'biaya'` dan `kategori = 'penghasilan'`, serta mendaftarkan seluruh nama teks yang pernah dipakai secara eksplisit dalam SQL. Ditambah explanatory tooltip (atribut `title`) pada tabel Audit Center — misalnya jurnal bersaldo Rp 0 biasanya memang status dokumen masih *draft/order*, bukan faktur utama.

**Kamus pemetaan COA tervalidasi 100% (2022):**

| Alias teks lama (bulanan) | Padanan COA resmi (tahunan) |
| :--- | :--- |
| `biaya usaha` | **6010** |
| `biaya umum` | **6030** |
| `biaya gaji` | **6050** |
| `biaya bpjs` | **6080** |
| `biaya` (string mentah) | **6100010** |
| `beban lain lain` | **7020010** |
| `laba lain lain` | **7010150** (terdapat anomali 30 juta di raw DB) |
| `jasa kirim` | **7010130** |
| `efisiensi biaya` | **3020010** |
| `kerugian` | **7020020** |
| `kerugian kurs` & `keuntungan kurs` | **7010080** |

Berkas diubah: kedua view di atas + `w:\new_san_variant\bi_reports\assets\app.js`.

### 8.7 Rekonsiliasi Order Penjualan Compare (Per-salesman)

**Konteks:** 18 September 2026, menu `laporan/PenjualanCompare/vieworderpenjualan`, workspace `w:\san_1agus` & `z:\san` (server `192.168.11.100`).

**Tiga kegagalan yang ditemukan:**

1. **Salesman Adam, April 2026:** tabel utama mencatat **Rp 12.070.792**, rincian popup **Rp 30.307.414**. Akar: `DataOutstanding.php::callPerSeller()` memakai penugasan biasa (`=`), sehingga transaksi PT. SAN (Rp 18,23 juta) tertimpa transaksi PT. Lavanya (Rp 12,07 juta).
2. **Kategori atas pada `viewOrderDetil` bernilai 0:** mapping key mencari kode surat jalan (`spd`) bukan sales order (`so`).
3. **Salesman lain tertimpa:** Janson (972) April 2026 tercatat Rp 140 juta, seharusnya Rp 522 juta.

**Perbaikan:** `DataOutstanding.php` mengubah `=` menjadi `+=` dengan default `0` pada baris **1865-1868, 1903-1906, 1944-1947, 1975-1978, 3172, 3234**. `PenjualanCompare.php`: `callOrderanPenjualans` membaca `saldo_order` dari `588so`/`582so`/`382so` (tanpa `582spo`) dengan format bulan 2-digit; `viewOrderDetil` mengarahkan mapping kategori atas ke kode SO sah dan menyelaraskan formula netto; `callOrderPenjualanTransaksi` memperbaiki typo `"382sso"` → `"382so"`. Tombol `Sync`/`doSync()` tetap aman (hanya MTD bulan berjalan). `MdlReporting.php` mempertahankan gembok historis `$last_update_bln == $bln_now` sehingga bulan lampau yang sudah closing terlindungi.

**Nilai aktual 25 salesman April 2026 di `192.168.11.100`:** Adam (908) Rp 30.307.414 (Order All & Netto), Adiwirya Hadisaputra (77) Rp 3.878.192.165, Suyanto (576) Rp 717.112.131, Paulus H. Welang (61) Rp 626.933.454, David B. Purwohadi (65) Rp 47.675.676, Markoni (73) Rp 144.865.602, Giovanny Amandha (1286) Rp 160.897.300, Rahmiati (1347) Rp 970.500.196, Janson (972) Rp 522.829.321, Oscar (925) Rp 375.044.190, Ansory Fauzi (1512) Rp 93.224.331.

---

## 9. Executive Dashboard

### 9.1 Fondasi Arsitektur

Controller `ExecutiveDashboard` (render + endpoint JSON + sanitasi filter) dan model `ExecutiveDashboardModel` berbasis data real DB. Domain dipetakan dari `heTransaksi_ui` ke tiga nilai: `sales`, `purchasing`, `expenses`. Payload dipecah menjadi lima blok: `kpiData`, `chartData`, `reportListData`, `tableData`, `alertsInsights`. Report center menyediakan *featured reports* + *all reports selector*. Stack visual: **ECharts** + **Tabulator** (dipindah ke asset lokal `assets/tabulator-master`), CSS scoped + JS modular (filter, export, drill-down). Akses dijaga config `executiveDashboardAllowedGroups` (backward compatible). Sinkronisasi KPI payable mengikuti `transaksi_payment_source`; validasi KPI/insight memakai `ComRekening`/`rek_cache` + flag `acc_coa`.

### 9.2 Workstream Ledger

- **B1** Snapshot kontrol total (trial balance debet/kredit/gap).
- **B2** Total kategori ledger (aset/liabilitas/ekuitas/pendapatan/beban).
- **B3** KPI gap transaksi vs ledger.
- **B4** Ledger historis **as-of-date** mengikuti `date_to` (bukan snapshot forever), dengan fallback aman ke snapshot bila histori kosong.
- **B5** Rekonsiliasi aging receivable/payable diseragamkan ke `transaksi_payment_source.sisa` + peta due date (`transaksi_due_date`, fallback `extern_date2`/`dtime`) as-of `date_to`.

### 9.3 Report Engine: 79 Kode Aktif

Resolusi `report_code → query builder` khusus, skema kolom Tabulator spesifik per report, filter row-level generik (customer/supplier/product/category/salesperson/department/branch/employee), export CSV/JSON dengan struktur kolom report-specific, dan drill-down ke transaksi sumber untuk report transaksional prioritas. Rincian 79 kode:

- **D. Standard ERP Reports (28):** D01 Sales Summary, D02 Sales by Customer, D03 by Product, D04 by Category, D05 by Salesperson, D06 Daily, D07 Monthly, D08 Sales Return, D09 Open Sales Order, D10 Invoice Summary, D11 Outstanding Receivable, D12 Purchase Summary, D13 by Supplier, D14 by Item, D15 by Category, D16 Open Purchase Order, D17 Goods Receipt, D18 Supplier Invoice, D19 Outstanding Payable, D20 Purchase Return, D21 Expense Summary, D22 by Category, D23 by Department, D24 by Branch, D25 by Employee, D26 Monthly Operating Expense, D27 Budget vs Actual Expense, D28 Recurring Expense.
- **E. Analytical (16):** E01 Sales Growth Comparison, E02 Revenue Trend Week/Month/Quarter, E03 Average Order Value, E04 Customer Repeat Order, E05 Top 20 Customer, E06 Low Performing Product, E07 Discount Impact, E08 Cancelled Sales Order, E09 Supplier Purchase Trend, E10 Supplier Price Comparison, E11 Purchase Lead Time, E12 On-time Delivery, E13 Fixed vs Variable Expense, E14 Cost Center Performance, E15 Expense Trend by Department, E16 Non-routine Expense.
- **F. Exception (12):** F01 Negative Margin Sales, F02 Below-standard Price Sales, F03 High Discount Transactions, F04 Duplicate Expense Detection, F05 Expense Outlier, F06 Suspicious Vendor Billing, F07 Purchase Price Jump, F08 Unusual Return Transaction, F09 Overdue Purchase Order, F10 Receivable Aging, F11 Payable Aging, F12 Open Order Aging.
- **G. Advanced (23):** G01–G03 Gross Margin by Customer/Product/Salesperson, G04 Contribution Margin, G05 Customer Profitability Matrix, G06 Product Profitability Matrix, G07 Branch Profitability, G08 Department Efficiency, G09 Sales vs Purchase Correlation, G10 Expense to Revenue Ratio, G11 Supplier Concentration Risk, G12 Customer Concentration Risk, G13 Stock Purchase Without Sales Movement, G14 Dead Product Purchase, G15 Slow-moving Purchase Analysis, G16–G18 Sales/Purchase/Expense Forecast, G19 Budget Burn Rate, G20 Month-end Projection, G21 Cash Outflow Projection, G22 Customer Lifetime Value Estimation, G23 Supplier Performance Scorecard.

### 9.4 Hasil Benchmark H1 (DB `san_13mar` @ `192.168.5.14`, generated `2026-04-11 01:25:29`)

**Scope data:** 198.424 transaksi aktif, rentang `2019-01-16` s/d `2026-04-10`, 7.797 baris `sisa > 0`, cabang terbesar `1` (`jakarta`) dengan 45.349 transaksi aktif.

| Query | AVG (ms) — semua cabang | Rows | AVG (ms) — cabang terbesar | Rows |
| :--- | ---: | ---: | ---: | ---: |
| `ledger_ytd_pl` | **4422,48** | 83 | 4263,49 | 12 |
| `table_negative_margin` | 2520,84 | **0** | 2183,32 | **0** |
| `table_sales_summary` | 2048,82 | 17 | 1961,43 | 2 |
| `kpi_sales_total` | 1896,02 | 1 | 1914,28 | 1 |
| `kpi_expense_total` | 1884,76 | 1 | 1866,86 | 1 |
| `table_purchase_summary` | 425,90 | 200 | 15,15 | 0 |
| `kpi_purchase_total` | 369,41 | 1 | 23,70 | 1 |
| `ledger_latest_harian` | 133,44 | 409 | 188,86 | 71 |
| `table_payable_aging` | 72,70 | 1000 | 56,96 | 2 |
| `table_receivable_aging` | 69,04 | 518 | 62,15 | 278 |

**Temuan performa:** `ledger_ytd_pl` (±4,4 detik) adalah bottleneck utama dan **tidak membaik dengan pembatasan cabang** (4263 ms untuk 12 baris) — indikasi biaya tetap materialisasi ledger, bukan pemindaian tabel. `table_negative_margin`花了 2,1–2,5 detik untuk **0 baris** — kandidat optimasi pertama.

### 9.5 Hasil Rekonsiliasi H2 (periode `2026-04-01` s/d `2026-04-10`)

Ringkasan **12 PASS / 0 FAIL**.

| Check | Semua cabang | Cabang terbesar |
| :--- | ---: | ---: |
| `activitySales` | 0 (PASS) | 0 (PASS) |
| `activityPurchase` | 1.033.019.333,28 (PASS) | 0 (PASS) |
| `activityExpense` | 0 (PASS) | 0 (PASS) |
| `neracaDebet` = `neracaKredit` | 796.151.268.797,79 (PASS) | 226.646.259.878,92 (PASS) |
| `rugilabaProfitEstimate` | 1.467.272.501,63 (PASS) | 3.291.575.014,27 (PASS) |

Catatan sumber: Neraca dari `_rek_master_cache` periode `forever` (jalur existing `ComRekening::fetchAllBalances_raw`); RugiLaba dari ledger + mapping `acc_coa`; Activity dari `transaksi` aktif sesuai `heTransaksi_ui`. Tabel materialisasi `neraca`/`rugilaba` terdeteksi tidak realtime (maks `2019-12-02`) sehingga tidak boleh dipakai sebagai sumber KPI.

### 9.6 Status QA/UAT

H1 (performa query dataset besar) dan H2 (konsistensi angka vs Neraca/RugiLaba/Activity) **selesai** dengan evidence `docs/executive-dashboard-h1-h2-report.md` + `.json`. **Belum dikerjakan:** H3 uji role permission per group user, H4 uji fallback data kosong/inkonsisten, H5 dokumentasi user guide, H6 sign-off finance/purchasing/sales/management.

---

## 10. Dev Jurnal & Perbandingan BI

### 10.1 Linimasa Penyelesaian (Agent 7, Antigravity)

| Tanggal | Topik | File/D Objek | Hasil Terukur |
| :--- | :--- | :--- | :--- |
| 2026-07-03 | Implementasi 10-Mode Sales Analytics & perbaikan nilai 0 | `vw_bi_sales_monthly`, `MdlRugilaba.php`, `Bi.php`, `bi_graph_sales.php` | Grafik bulanan statis → 10 mode + DataTable |
| 2026-07-05 | Laporan Pivot Aktivitas Personil | `Activity_model.php`, `eusvc/LaporanApi.php`, `bi_reports/index.html`, `app.js`, `scratch/setup_activity_db.php`; `dim_person`, `trx_activity_raw`, `vw_activity_detail`, `vw_activity_daily`, `SP_GET_PIVOT_ACTIVITY` | 95.162 baris / 73 personil; 4 mode SP OK |
| 2026-07-05 | Universal Human Participation Analytics | `scratch/setup_human_db.php`, `Human_model.php`, `eusvc/HumanApi.php`, `bi_reports/index.html`, `style.css`, `app.js`; `trx_human_activity_timeline`, `trx_human_pivot_cache` | 590.681 baris ETL ~20 s; `get_list` 1,21 ms |
| 2026-07-06 | UI Hybrid hover kontribusi cabang (restorasi 12 kolom) | `MdlRugilaba.php`, `Bi.php`, `bi_graph_sales.php` | Helper `_branchCell()`, `_cumBranchCell()` |
| 2026-07-06 | Tabel validasi harga produk 4 kolom | `Human_model.php` (`run_human_etl()`, `sync_incremental()`), `app.js` (`formatProductSummaryTable()`, `renderModalTopProducts()`) | Regex harga dinamis; fallback 2 kolom |
| 2026-07-06 | View `vw_consolidate_monthly` untuk data real-time | `vw_consolidate_monthly`, `MdlRugilaba.php` (3 method) | Juli 2026 tidak lagi Rp 0 |
| 2026-07-07 | Perbaikan double-counting & mismatch cabang | `MdlRugilaba.php::getSalesBranchBreakdown()` | Selisih 7,6 miliar → **0,00** |
| 2026-07-10 | Rekonsiliasi P&L tahunan vs bulanan (data 2022) | `vw_consolidate_monthly`, `vw_bi_sales_monthly`, `bi_reports/assets/app.js` | Beban Rp 9,2 M + pendapatan Rp 2 M semula hilang |

### 10.2 Detail `vw_consolidate_monthly`

Dibuat untuk menggantikan ketergantungan pada cache `rugilaba` yang hanya terupdate saat *Monthly Closing* —ITs juga menusul `Rugilaba/viewPLConsolidatedNew` yang bersifat live. View menghitung langsung dari `jurnal`: saldo penjualan kotor, return, HPP, efisiensi, jasa kirim, selisih kurs/opname, biaya operasional (COA 6%), serta pendapatan/beban non-operasional.

**Dua filter kritis yang wajib dipertahankan:**

1. `jenis != 'Jurnal Penutup'` — mencegah jurnal penutupan tahunan merusak akumulasi bulanan riil.
2. `transaksi_no NOT LIKE '1000.%' AND transaksi_no NOT LIKE '1001.%'` — mencegah saldo awal tahunan (*opening balance*) merusak saldo penjualan Januari. Tanpa filter ini saldo Januari menjadi minus akibat jurnal balik saldo awal.

### 10.3 Matriks Perbandingan Angka BI

| Aspek | Executive Dashboard | BI 10-Mode (RugiLaba/Jurnal) | Dashboard Sales (`z_sales_*`) | Dashboard 7 Kartu (`Graph.php`) |
| :--- | :--- | :--- | :--- | :--- |
| Sumber | `transaksi` via `heTransaksi_ui` + `_rek_master_cache` | `vw_consolidate_monthly` ← `jurnal`; `vw_bi_sales_monthly` ← `rugilaba` | `z_sales_salesman_cache` (header), `z_sales_pembantu_cache` (item) | `_rek_master_cache`, `run_san_report.penjualan_order`, `z_sales_salesman_cache`, `_rek_pembantu_penjualan_cache` |
| Definisi penjualan | Domain `sales` pada transaksi | `penjualan_net` = penjualan + return + jasa kirim | `harga_netto` pada rekening `582so`/`382so`/`588so` (atau `582spd`/`382spd` untuk IFRS) | Kartu (B) Order = Order − Reject − Closed |
| Definisi beban | Domain `expenses` pada transaksi | `total_biaya` (`kategori='biaya'`), multiplier `-1` | — | — |
| Latency | Near-real-time | `vw_consolidate_monthly` real-time; `vw_bi_sales_monthly` per closing | Cache sinkron | Snapshot per tanggal + live query |
| Guardrail | Rekonsiliasi as-of `date_to`, H2 12 PASS | 3-Way-Matching anomali + dedup MAX | 3-Way-Matching pada kartu (D) | Proteksi 3-Way-Matching di `callSummary()` |

**Titik gagal yang berulang di semua kolom:** setiap kolom "yang benar" menurut definisi lokalnya, sehingga tiga dashboard dapat menampilkan angka penjualan berbeda tanpa satu pun menandainya salah. Standar yang belum ada adalah **satu definisi omset kanonik + satu tabel konvergensi**.

### 10.4 Blueprint Pendukung: Validasi AI 3-Way-Matching & HITL

`blueprint-ai-problem-solving.md` menyediakan kerangka yang dipakai sebagai pola validasi yang sama oleh proteksi dashboard: alur *Data Input → Prompt & AI Processing → Data Output* divalidasi melalui **Three-Way Matching** terhadap (A) dokumen eksternal, (B) database internal, (C) aturan bisnis. Kelayakan dinilai **9/10** dengan rekomendasi *Human-in-the-Loop* sebagai katup pengaman.

Aturan *alignment* yang relevan untuk pelaporan: tidak boleh ada pembulatan mandiri pada angka desimal (`1500.50` ≠ `1501`); kolom wajib kosong wajib memicu `REJECT`; tidak boleh berasumsi data di luar konteks input. Kamus data: `source_document` (PDF/file), `internal_reference_db` (JSON/table), `status_verifikasi` (`VALID`/`REJECT`), `catatan_anomali` (alasan spesifik bila `REJECT`).

### 10.5 Blueprint Pendukung: Modul `produksiproses`

`blueprint_produksiproses.md` berada di luar lingkup pelaporan tetapi memengaruhi angka HPP pada P&L. Relevant karena `HPP` masuk ke `Laba Kotor` pada seluruh mode BI. `jenisTr`: `776` (Master Work Order), `7761` Persiapan & Pemotongan, `7762` Assembling & Pengelasan, `7763` Finishing & Coating, `7764` QC & QA, `7765` Final Packaging & Serah Terima FG. `_processSelectProductAssembling::selectFase()` menarik alokasi BOM bervarian per fase dan mentransfer akumulasi nilai WIP ke fase berikutnya; `FollowUp::doFollowup()` memanggil `ComLockerStockDualWrite::pair()` (dual-write stock rebalancing) dan `ComJurnal` untuk jurnal *Persediaan WIP (D) vs Bahan Baku (K) & Beban Operasional Produksi*. Implikasi analitik: HPP `7765` masuk `hpp`/`total_hpp` pada `vw_bi_sales_monthly`, sehingga **`total_hpp` yang dihitung di Controller harus_two-way konsisten dengan HPP yang dicatat `ComJurnal` per fase** — hal ini tidak dibahas di dokumen mana pun.

---

## 11. Temuan Lintas-Dokumen & Kontradiksi

### 11.1 Kontradiksi Faktual

**K1 — Definisi "omzet/realisasi sales" tidak tunggal (paling kritis).**
`comprehensive_sales_report_blueprint.md` M1 menetapkan recognisi pendapatan **hanya pada tahap SPD/Delivered** (`rekening IN ('582spd','382spd')`) demi IFRS 15/PSAK 72. `z_sales_salesman_cache_analysis.md` §2 menetapkan bahwa laporan realisasi sales **harus memfilter SO saja** (`582so`,`382so`,`588so`) untuk menghindari *double counting*. Keduanya benar menurut definisi masing-masing, tetapi menghasilkan angka omset yang berbeda pada periode yang sama. Belum ada dokumen yang menetapkan mana yang menjadi sumber KPI resmi.

**K2 — Definisi `total_biaya` berubah tiga kali dalam 8 hari.**
(1) Definisi awal `kategori='biaya' AND rekening NOT IN ('hpp','hpp projek','5010','return penjualan')`; (2) 2026-07-07 dibatasi `rekening LIKE '6%'` + `kategori='biaya'` setelah selisih 7,6 miliar; (3) 2026-07-10 `'6%'`/`'7%'` dibuang demi auto-catch `kategori='biaya'`/`'penghasilan'`. Versi (2) dan (3) bertolak langsung: (2)andu berbasis prefiks, (3) berbasis kategori. Jurnal 2026-07-10 tidak menyebut konsekuensi terhadap `getSalesBranchBreakdown()` yang justru diselaraskan ke (2).

**K3 — `penjualan_net` inklusif vs eksklusif `jasa kirim`.**
`vw_bi_sales_monthly` menghitung `penjualan_net` dengan `rekening IN ('penjualan','penjualan projek','4010','return penjualan','jasa kirim')`, sementara `getSalesBranchBreakdown()` (pasca 2026-07-07) **menghapus** `'jasa kirim'` dari `penjualan_net` dengan alasan sudah ada metrik `jasa_kirim` terpisah. View dan model kini berbeda definisi untuk kolom bernama sama.

**K4 — Makna kode `7020020` berbeda.**
`dev-jurnal-bi.md` (2026-07-07) menyebut `7020020` sebagai **selisih opname** non-operasional yang salah masuk `total_biaya`. `dev-jurnal-laporan-bi.md` memetakan alias teks **`kerugian`** ke COA **`7020020`**. Satu kode, dua label; belum jelas apakah `selisih opname` = `kerugian` atau kebetulan koekisten.

**K5 — Blueprint `blue-print-laporan-person.md` masih memuat dua bug yang sudah dikoreksi.**
Regex blok masih `'/\[(.*?)\|olehID\]/s'` (greedy) padahal jurnal 2026-07-05 menyatakan fix ke `'/\[([^\]]+)\|olehID\]/'`; dan `SP_GET_PIVOT_ACTIVITY` masih meng-`CONCAT` `@date_group` **setelah** `SUM(...)` yang disebut sebagai penyebab `Unknown column 'tahun'`. whoever menjalankan blueprint itu akanHt reintroduce ketiga bug.

**K6 — `vp_contoh` pivot C tidak akan pernah match.**
`z_sales_salesman_cache_analysis.md` Query C menulis `'Adiwirya Hadisaputra '` (spasi akhir), `'Yanty '` (spasi akhir), `'Suyanto'`. `KONTEKS_HANDOVER_LAPORAN_PENJUALAN.md` mencatat nama yang sama **tanpa** spasi akhir. Dengan operator `=` pada CASE, nilai tidak akan cocok dan kolom tersebut **senyap bernilai 0** — pola kegagalan yang sama seperti kartu-kartu nol di §11.3.

**K7 — Scale dataset ETL personil berbeda drastis.**
`dev-jurnal-human-analytics.md`: 398.126 transaksi → **590.681** baris keterlibatan. `dev-jurnal-bi.md` (pivot aktivitas): 96.915 transaksi → **95.162** baris. Keduanya membaca sumber yang sama (`transaksi`) pada DB `san_13mar`, tetapi cakupan, cakupan jenis_master, dan waktu eksekusi berbeda (Juli 2026 vs Juli 2026). Angka keduanya tidak boleh dibandingkan; tidak ada dokumen yang menyatakan perbedaan cakupan ini.

**K8 — Trail database dan workspace tidak konsisten.**
Satu sintesis ini mencakup: `san_13mar` @ `192.168.5.14` (BI 10-mode, Executive Dashboard, `z_sales_*`), `run_san_report.penjualan_order` @ `192.168.11.100` (dashboard 7 kartu,andover order), workspace `z:\san_11sep` (analisis 20 Sep 2026), `w:\san_1agus` (PenjualanCompare), `w:\new_san_variant` & `c:\xampp\htdocs\new_san_variant` (produksiproses/variant). Modul `bi_reports` sendiri muncul pada tiga path berbeda.

**K9 — Timeline anomali 948 M bertabrakan dengan handover 18 Sep 2026.**
Handover 18 Sep 2026 menyatakan seluruh 25 salesman April 2026 sudah disinkronkan ke nilai aktual. Analisis 20 Sep 2026 masih menemukan snapshot `2026-09-30` terduplikasi几十 kali pada `run_san_report.penjualan_order`. Artinya perbaikan April tidak menutup celah INSERT-yang-tanpa-`UNIQUE KEY` untuk periode berikutnya — guardrail 3-Way-Masking menutup gejala, bukanlubang.

### 11.2 Inkonsistensi Minor antar-Dokumen

- **I1** Label bulan `'Agu'` (bukan `'Agu'`/`'Agustus'`) pada Query D pivot bulanan `z_sales_salesman_cache_analysis.md` — kolom akan selalu 0 bila incoming `bln` berupa teks lengkap.
- **I2** `executive-dashboard-checklist.md` menyatakan evidence H2 "generated `2026-04-11 01:21:04`" sementara `executive-dashboard-h1-h2-report.md` mencantumkan "Generated at: `2026-04-11 01:25:29`". Dua report dalam 4 menit; checklist mengacu file yang berbeda.
- **I3** `blueprint-perubahan-segment-key.md` §3 menyebut "Coms models: 8 file" dan "Total ~22 file", sedangkan Fase 2 yang sudah *DONE* menubar 18 item yang memuat **12 file Coms** + 3 Mdls + 3 PreProcs. Angka 8 tidak cocok dengan 12.
- **I4** Fase 3–5 `blueprint-perubahan-segment-key.md` menyEnumerasi file **per modul** (`distribusifg`, `opname`, `pembelian`, `pembelianimport`, `pindahgudang`, `requeststok`) sedangkan §5.3–5.7 menyebut file tunggal tanpa qualifier modul. Membaca checklist tanpa §3 akan-follower mengira hanya 5 file controller yang tersisa, bukan 20+.
- **I5** `dev-jurnal-bi.md` berisi blok SQL view **duplikat** (baris 14–57 dan 70–114 identik), hanya blok pertama yang terpotong tanpa `ORDER BY`. Risiko dokumentasi, bukan risiko kode.
- **I6** `blueprint-bi-10mode-analytics.md` menyebut modul "ugilaba"/tabel `rugilaba`; `dev-jurnal-bi.md` menyebut tabel yang sama. Konsisten, tetapi penamaan modul BI (`bi_reports`) berbeda dari penamaan modul transaksi (`ugilaba`/`rugilaba`).
- **I7** `comprehensive_sales_report_blueprint.md` M3 RFM memakai `GROUP BY customer_nama, seller_nama` (segmentasi **per pasangan customer–salesman**), sedangkan `z_sales_salesman_cache_analysis.md` RFM memakai `GROUP BY customer_nama` saja (segmentasi **per customer**). Threshold identik, hasil berbeda.
- **I8** Modul M2 menampilkan `Conversion_Rate` dengan pembagi `COUNT(DISTINCT ... rekening IN ('582spo','382spo'))` yang dijaga `HAVING Total_PreOrders_SPO > 0`, tetapi alias tersebut merujuk hasil `COUNT(DISTINCT CASE …)` di level view yang sudah ter-dedup per `rekening` — bukan per salesman. Rasio dapat Turns > 100% bila satu dokumen punya beberapa baris SO/SPO berbeda tanggal.

### 11.3 Temuan Kualitas Bukti

- **H2 "12 PASS / 0 FAIL" lemah secara statistik.** Pada scope semua cabang, `activitySales = 0`, `activityExpense = 0`; pada scope cabang terbesar, **kelima** metrik selain neraca bernilai 0. Yang benar-benar menguji konsistensi numerik hanyalah `activityPurchase` (1.033.019.333,28), `neracaDebet = neracaKredit` (tautologi yang harus bernilai sama), dan `rugilabaProfitEstimate`. Rekomendasi: H4 (fallback data kosong) justru belum dikerjakan, padahal itu akan membuktikan apakah nilai 0 adalah benar atau artefak.
- **`table_negative_margin` = 2.520,84 ms untuk 0 baris.** Kandidat optimasi dengan ROI tertinggi di Executive Dashboard, dan tetap ditandai "selesai" pada checklist.
- **`ledger_ytd_pl` ≈ 4,4 detik dan tidak membaik saatPsychrestricted cabang** (4.263 ms untuk 12 baris) — biaya tetap materialisasi, butuh indeks atau pre-aggregation.
- **Rantai proteksi berlapis tanpa perbaikan sumber.** (a) dedup `MAX()` di view, (b) proteksi 3-Way-Matching di `Graph.php`, (c) konfirmasi COA di `app.js`, (d) tooltip Audit Center. Semua aktif; **tidak ada satu pun** yang memperbaiki `seller_id` yang ter-comment atau menambahkan `UNIQUE KEY`. Selama duacacat itu masih ada, guardrail hanya memilih angka mana yang ditampilkan, bukan membuat angka itu benar.

---

## 12. Lampiran: Daftar File Sumber

| Nama File | Ukuran | Baris | Topik H1 | Peran dalam Sintesis |
| :--- | ---: | ---: | :--- | :--- |
| `comprehensive_sales_report_blueprint.md` | 8.565 B | 195 | Blueprint: Dashboard BI & Laporan Penjualan Komprehensif (ISO & IFRS Compliant) | Sumber §4: 4 modul laporan, view `vw_sales_header_clean`/`vw_sales_item_clean`, formula IFRS-15, RFM, UI/UX |
| `blueprint-bi-10mode-analytics.md` | 4.729 B | 88 | Blueprint: BI 10-Mode Sales Analytics Architecture | Sumber §5: rantai `rugilaba → vw_bi_sales_monthly → MdlRugilaba → Bi.php → bi_graph_sales.php`, definisi 10 mode |
| `dev-jurnal-bi.md` | 14.952 B | 216 | Dev Jurnal - Upgrade BI Graph Sales to 10-Mode Analytics | Sumber §5.5–5.6, §10.1: hover kontribusi cabang, perbaikan 7,6 miliar, pivot aktivitas personil, regex/SP fix |
| `dev-jurnal-laporan-bi.md` | 3.291 B | 43 | Dev Jurnal: Laporan Bi/viewGraphSalesAnalytics | Sumber §8.6: mismatch P&L 2022, hardcode `LIKE '6%'`/`'7%'`, kamus pemetaan COA |
| `dev-jurnal-human-analytics.md` | 7.104 B | 71 | Jurnal Pengembangan - Universal Human Participation Analytics | Sumber §6: 2-tier store, 590.681 baris, `HumanApi.php`, tabel harga 4 kolom, `vw_consolidate_monthly` |
| `executive-dashboard-checklist.md` | 6.387 B | 139 | Executive Dashboard Checklist (Full Scope) | Sumber §9.3, §9.6: status A–H, 79 report code, H3–H6 terbuka |
| `executive-dashboard-h1-h2-report.md` | 4.119 B | 78 | Executive Dashboard H1/H2 Report | Sumber §9.4–9.5: benchmark 10 query, rekonsiliasi 12 PASS / 0 FAIL, catatan `neraca`/`rugilaba` tidak realtime |
| `blue-print-laporan-person.md` | 17.359 B | 468 | Laporan Pivot Aktivitas Transaksi Berbasis Step & Personil | Sumber §7.1: `dim_person`, `trx_activity_raw`, `SP_GET_PIVOT_ACTIVITY`, `Activity_model.php`, `Laporan.php` — **kandung bug yang sudah dikoreksi (K5)** |
| `blueprint-perubahan-segment-key.md` | 15.484 B | 365 | BLUEPRINT: PENYERAGAMAN KEY VARIANT (FORMAT 3 SEGMEN) | Sumber §7.2: `parse_variant_key()`, 18 file Fase 2, collision `variant:7`, risiko session, Fase 3–5 |
| `analisis_dashboard_dan_duplikasi_data.md` | 11.703 B | 228 | Laporan Analisis Sumber Data Dashboard & Forensik Duplikasi Data | Sumber §8.1–8.5: 7 kartu `Graph.php`, rekonsiliasi 94,6 M vs 948 M, 5 root cause, proteksi 3-Way-Matching |
| `z_sales_salesman_cache_analysis.md` | 17.061 B | 390 | Analisis Tabel Database `z_sales_salesman_cache` & Formula Laporan Pivot Sales | Sumber §7.3: profil 18.525/8.111 baris, siklus `rekening`, 4 formula pivot, pending approach A/B |
| `KONTEKS_HANDOVER_LAPORAN_PENJUALAN.md` | 3.158 B | 53 | KONTEKS & HANDOVER: PERBAIKAN LAPORAN PENJUALAN COMPARE & SINKRONISASI | Sumber §8.7: `DataOutstanding::callPerSeller` `=` → `+=`, typo `382sso`, 11 nilai salesman April 2026 |
| `blueprint-ai-problem-solving.md` | 6.530 B | 132 | BLUEPRINT PERANCANGAN DAN VALIDASI SISTEM AI | Sumber §10.4: pola 3-Way-Matching + HITL, aturan alignment anti-pembulatan & anti-halusinasi |
| `blueprint_produksiproses.md` | 5.450 B | 112 | BLUEPRINT CETAK BIRU MODUL: PRODUKSI PROSES (MULTI-PHASE MANUFACTURING) | Sumber §10.5: `jenisTr 776`–`7765`, `selectFase()`, WIP transfer, `ComJurnal` — memengaruhi `hpp`/`total_hpp` pada P&L BI |

**Total: 14 file · 133.676 byte · 2.578 baris.**