# Resolusi DO by SO Qty pada Dashboard (`DashboardController`)

## Latar Belakang
Pada Dashboard Salesperson, metrik **SO Qty (kg)** dihitung dari jadwal pengiriman di tabel `rpt_et_getdobyso` pada bulan berjalan.
Sebelum perbaikan (Prompt 32), resolusi PIC dilakukan di tingkat **customer** (menggunakan transaksi SO terakhir customer), bukan di tingkat **dokumen SO**.

### Masalah yang Terjadi
Jika sebuah customer pernah memiliki transaksi dengan beberapa salesperson berbeda:
1. Customer Itochu Corporation TOKTG (`0001300136`) memiliki transaksi lama/lain yang dibuat oleh Gwen (`user_id = 38`).
2. Ayreen (`user_id = 49`) membuat SO `0112607016` untuk Itochu pada September 2026 (berat total 10.832,40 kg).
3. Karena customer Itochu diasosiasikan secara global ke Gwen (transaksi ID terbaru di sistem), seluruh DO Qty Itochu dialokasikan ke Gwen, sehingga pada dashboard Ayreen:
   - Kolom SO Qty customer Itochu bernilai `0.00 kg`.
   - Modal popup rincian menampilkan 4 baris jadwal delivery (2.853,00 + 2.563,20 + 2.853,00 + 2.563,20), tetapi **Total** di footer bernilai `0.00`.

---

## Logika Resolusi Kepemilikan DO Dokumen (`getResolvedDoBySoData`)

Setiap nomor dokumen SO (`SALESDOCUMENT`) pada `rpt_et_getdobyso` dipetakan ke PIC/salesperson dengan urutan prioritas:

1. **Prioritas 1: Dokumen Sistem (`trs_header_data` + `trs_so_document`)**
   - Diambil dari header aktif terbaru per `SALESDOCUMENT`.
   - Pemilik SO adalah `COALESCE(t.temp_status_by, t.final_status_by)` (`user_id`).
   - Customer adalah `h.SOLDTO`.

2. **Prioritas 2: Dokumen SAP Billing (`rpt_ET_ZVBILLING`)**
   - Jika dokumen tidak ada di sistem SO, dicek ke data billing SAP.
   - Pemilik SO adalah `b.SLSMAN` (`slsman`).
   - Customer adalah `b.SOLDTO`.

3. **Prioritas 3: Fallback Dokumen Baru (`rpt_et_getsobyrange` - Prompt 29)**
   - Jika dokumen SO baru ditarik dan belum ada di sistem maupun billing, diambil data `SOLDTO` dari `rpt_et_getsobyrange`.
   - PIC fallback ditentukan dari transaksi terakhir customer tersebut:
     - Cek transaksi terakhir `trs_header_data` untuk customer tersebut (`user_id`).
     - Jika tidak ada, cek transaksi terakhir `rpt_ET_ZVBILLING` untuk customer tersebut (`slsman`).
     - Jika tidak ada di keduanya, dokumen diabaikan.

---

## Penggabungan Data ke Dashboard (`getCustomerActuals`)

1. Mengumpulkan semua customer yang memiliki DO Qty untuk salesperson aktif dari:
   - `$resolvedDoData['user_so_qty'][$userId]`
   - `$resolvedDoData['slsman_so_qty'][$salespersonName]`
2. Setiap customer yang memiliki DO Qty:
   - Jika customer sudah ada di `$customerMap`, nilai `so_qty` di-update dengan nilai aktual.
   - Jika customer belum ada di `$customerMap` (misal kasus qty-only SO dari SAP), customer baru ditambahkan ke `$customerMap` dengan `so_amount = 0.0` dan `so_qty = $qty`.

---

## Frontend Modal Popup (`resources/views/pages/dashboard/list.blade.php`)

Pada fungsi `buildSoTable(detail, customerName)`:
- Menghitung total kumulatif baris rincian delivery yang dirender (`computedQtyTotal`).
- Nilai total di footer menggunakan `detail.so_qty > 0 ? detail.so_qty : computedQtyTotal` untuk memastikan nilai footer selalu sinkron dengan baris tabel.

---

## Tampilan Dashboard per CUSTOMER - ENDUSER (Prompt 34)

### 1. Penentuan End User per Dokumen SO
Baik pada data pre-cutoff maupun post-cutoff, penentuan `ENDUSER` per `SALESDOCUMENT`:
- **Prioritas Utama**: Kolom `ENDUSER` pada tabel `rpt_et_getsobyrange`.
- **Fallback**: Kolom `ENDUSER` pada header aktif terbaru di `trs_header_data` jika di `rpt_et_getsobyrange` masih kosong atau belum tersinkronisasi.
- Jika keduanya kosong, maka `ENDUSER` dianggap kosong (ditampilkan `-` di antarmuka).

### 2. Pengelompokan Baris Dashboard (`row_key`)
- Baris tabel dashboard per divisi (Hygiene, Medical, Industrial) dikelompokkan berdasarkan pasangan unik `(CUSTOMER_CODE, END_USER)` dengan format key: `{$customer_code}###{$end_user}`.
- Jika satu customer memiliki beberapa SO dengan end user berbeda (contoh: Sakai Trading Co., LTD. dengan `KAO IW TOKYO` dan `KAO GATHER`), data akan dipecah menjadi baris tersendiri sesuai masing-masing end user.
- Metrik `so_amount`, `so_qty`, `actual_amount`, `actual_qty`, dan `on_hand` dihitung spesifik untuk masing-masing kombinasi Customer - End User.

### 3. Penambahan Kolom End User di UI
- Tabel divisi pada `resources/views/pages/dashboard/list.blade.php` memiliki kolom tambahan **End User** di sebelah kolom Customer Name.
- Trigger modal detail (`detail-trigger`) menggunakan key `row_key`, sehingga popup rincian menampilkan dokumen dan delivery order spesifik untuk kombinasi Customer - End User tersebut.
- Riwayat harga (`price-history-trigger`) tetap menggunakan `customer_code` murni karena grafik tren harga berlaku di tingkat customer.

---

## Filter Periode DO by SO & Resolusi PIC by End User (Prompt 35)

### 1. Filter Periode Berbasis `FISCALMONTH` & `FISYR` (`applyDoPeriodFilter`)
- Di tabel `rpt_et_getdobyso`, jadwal pengiriman ditarik dari SAP per periode fiskal (`IP_YEAR` & `IP_MONTH`).
- Dokumen tertentu (contoh: SO `0112607040`) memiliki baris jadwal pengiriman tanggal `2026-08-27` yang oleh SAP dimasukkan ke dalam periode `FISCALMONTH = 09` dan `FISYR = 2026`.
- Mengganti filter tanggal kalender (`DELIVERYDATE BETWEEN`) dengan `applyDoPeriodFilter`:
  - Utama: mencocokkan `FISCALMONTH = lpad($month, 2, '0')` dan `(FISYR = $year OR FISCALYEAR = $year)`.
  - Fallback: jika `FISCALMONTH` null/kosong, menggunakan rentang kalender `DELIVERYDATE`.
- Pada rincian popup modal, baris jadwal pengiriman dikelompokkan per `(SALESDOCUMENT, DELIVERYDATE)` dan diurutkan secara kronologis berdasarkan tanggal pengiriman (`delivery_date asc`).

### 2. Resolusi PIC Dokumen `rpt_et_getsobyrange` per `(CUSTOMER, ENDUSER)`
- Dokumen SO yang baru ada di `rpt_et_getsobyrange` (belum masuk `trs_header_data` maupun billing `rpt_ET_ZVBILLING`, contoh: `0112608018` dan `0112608019` untuk Sakai Trading Co., LTD. - `CRECIA`):
  - Resolusi PIC diprioritaskan mencari histori transaksi `trs_header_data` yang memiliki kesamaan **Customer dan End User** (`customerEndUserSystemPics`), bukan hanya customer saja.
  - Dengan demikian, dokumen untuk end user `CRECIA` diatribusikan ke **Ayreen** (`user_id = 49`), sedangkan end user `ICHIMURA SANGYO` diatribusikan ke **Selvie** (`user_id = 34`).
  - Total SO Qty Sakai Trading Co., LTD. (CRECIA) pada September 2026 tampil tepat `136.156,43 kg` (terdiri dari 7 baris pengiriman kronologis: 27.08, 04.09, 11.09, 17.09, 23.09, 24.09, 30.09).

---

## Pemulihan Popup Price Comparison & Pengurutan Kronologis Popup (Prompt 36)

### 1. Pemulihan Modal Popup Price Comparison & PP Resin
- Sebelumnya, pendaftaran payload `window.dashboardDetail` di-index oleh `row_key` (`customer_code###end_user`), sehingga lookup saat klik baris customer (`code = cell.dataset.code`) bernilai `undefined` dan popup tidak terbuka.
- Perbaikan:
  - Mendaftarkan entri `customer_code` langsung ke `window.dashboardDetail` berisi `price_history`.
  - Click listener dilengkapi fallback lookup untuk mencari entri berawalan `code + '###'`.
  - `loadPriceHistoryRange` memperbarui data riwayat harga pada semua entri terkait di `window.dashboardDetail`.
  - Modal grafik tren harga jual historis, kartu KPI, dan perbandingan PP resin cost kembali berfungsi normal.

### 2. Pengurutan Kronologis Berdasarkan Tanggal pada Seluruh Popup
- **Detail SO Qty**: Diurutkan berdasarkan `delivery_date` ascending.
- **Detail Sudah Dikirim (kg / USD)**: Diurutkan berdasarkan `delivery_date` (jadwal DO) atau `billdate` (billing) ascending.
- **Detail Belum Di Kirim (kg / USD)**: Diurutkan berdasarkan `delivery_date` ascending serta menampilkan kolom `Tgl Pengiriman`.
- **Rincian Titik Grafik Riwayat Harga**: Transaksi billing diurutkan berdasarkan `billdate` ascending, dan tabel PP resin diurutkan berdasarkan `effective_date` ascending.

---

## Logika End User & Klasifikasi Lokal/Export via `mst_account_group` pada Sales Report (`RptslsController` - Prompt 38)

### 1. Penentuan Customer Lokal vs Export
- Baik data pre-cutoff (`rpt_ET_ZVBILLING`) maupun post-cutoff (`trs_header_data`), kode customer `SOLDTO` dipetakan ke `vw_customers.kode_sap`.
- Dari `vw_customers.account_group`, di-join ke tabel master `mst_account_group.account_group` untuk membaca kolom `is_export`:
  - `is_export = 'Yes'` &rarr; **Export**
  - `is_export = 'No'` &rarr; **Lokal** (kecuali `0001100298` yaitu UCI &rarr; **Lokal (UCI)**)
- Menghilangkan ketergantungan lama pada hardcoded `DISTCH in ('40', '60')`.

### 2. Integrasi End User pada Laporan Penjualan (Rptsls)
- **Omzet Penjualan**: Setiap baris billing dipetakan ke `ENDUSER` melalui `getEndUsersBySalesDocs(SALESDOC)` (mengambil dari `rpt_et_getsobyrange`, fallback `trs_header_data`). Tabel rincian transaksi omzet menampilkan kolom **End User**.
- **Analisis Pareto**: Dikelompokkan per pasangan unik `(Customer, End User)`. Tabel analisis dan grafik Pareto menampilkan nama customer beserta End User-nya secara terpisah.
- **Export Excel**: File excel laporan Pareto dan Omzet menyertakan kolom **End User**.

### 3. Pemisahan Sub Divisi Hygiene dan Medical pada Omzet Penjualan
- Memisahkan kategori penjualan Hygiene dan Medical yang sebelumnya digabung menjadi `H & M`:
  - **Lokal Hygiene**: Penjualan lokal khusus sub divisi Hygiene (`H`).
  - **Lokal UCI**: Penjualan lokal khusus customer `0001100298` (*Itochu Indonesia* &rarr; tergolong Hygiene).
  - **Lokal Medical**: Penjualan lokal khusus sub divisi Medical (`M`).
  - **Lokal Industrial**: Penjualan lokal sub divisi Industrial (`I`).
  - **Export Hygiene**: Penjualan ekspor sub divisi Hygiene (`H`).
  - **Export Medical**: Penjualan ekspor sub divisi Medical (`M`).
  - **Export Industrial**: Penjualan ekspor sub divisi Industrial (`I`).
- **Penentuan Sub Divisi via `mst_salesperson_goals`**:
  - Sub divisi ditentukan berdasarkan mapping customer pada `salesperson_per_customer_goals` untuk tahun terkait.
  - Customer `0001100298` (*Itochu Indonesia*) secara eksplisit dipetakan ke **Hygiene** (`H`).
  - Customer divisi HM yang **belum di-mapping** pada master goals sementara ditampilkan pada **kedua sub divisi** (Hygiene dan Medical).
  - Laporan menampilkan komponen peringatan (alert box) yang memuat daftar nama dan kode customer HM yang belum terpetakan agar pengguna dapat segera melengkapi master target.

---

## Keselarasan 100% Query `rptsls` dengan `DashboardController` (Prompt 40)

### 1. Sumber Data Berat (Amount kg)
- **Data Post-Cutoff ($\ge 2026$)**:
  - Dihitung dari `rpt_et_getonhand` dengan kondisi `POSTGOODSISSUE = 'C'` (barang riil terkirim).
  - Setiap delivery baris memiliki `QUANTITY` (kg), `REFERENCE_DOCUMENT` (nomor SO), `CUSTOMERCODE` (customer), `DELIVERYDATE`, dan `DOCUMENTNUMBER` (No. DO).
- **Data Pre-Cutoff ($< 2026$)**:
  - Dihitung dari `rpt_ET_ZVBILLING.ACTWEIGHT` (faktur penjualan SAP historis, `DISTCHD != 'Waste'`).

### 2. Atribusi Berdasarkan Salesperson Penjual Dokumen SO
- **Post-Cutoff**:
  - Diambil dari dokumen SO di `trs_header_data` + `trs_so_document`:  
    `COALESCE(temp_status_by, final_status_by)` $\rightarrow$ `user_id` salesperson penjual.
  - Jika dokumen belum ada di sistem SO, fallback ke `rpt_ET_ZVBILLING.SLSMAN`, lalu ke `rpt_et_getsobyrange`.
- **Pre-Cutoff**:
  - Diambil dari `rpt_ET_ZVBILLING.SLSMAN` $\rightarrow$ dicocokkan ke User ID salesperson.

### 3. Klasifikasi Sub Divisi via `mst_salesperson_goals` Salesperson Terkait
- Salesperson penjual dicocokkan ke `mst_salesperson_goals` pada tahun terkait.
- Cek detail `salesperson_per_customer_goals` untuk kombinasi `(mst_salesperson_goals_id, customer_code)`:
  - Jika customer terdaftar untuk salesperson tersebut $\rightarrow$ gunakan `sub_division` yang ditentukan (`H`, `M`, atau `I`).
  - Contoh spesifik (Prompt 40):
    - SO yang dijual oleh Nike ke Customer A $\rightarrow$ Nike memegang HM dan di goals memetakan Customer A ke Medical $\rightarrow$ SO ini masuk ke **Medical** (`M`).
    - SO yang dijual oleh Zesta ke Customer A $\rightarrow$ Zesta memegang Industrial $\rightarrow$ SO ini masuk ke **Industrial** (`I`).
- Jika customer belum terdaftar di goals salesperson:
  - Customer group `C1`/`C2` atau salesperson ber-role Industrial (`shind1`, `manind`, `admind`) $\rightarrow$ **Industrial** (`I`).
  - Customer `0001100298` (*Itochu Indonesia*) $\rightarrow$ **Hygiene** (`H`).
  - Customer group `A1`/`A2` $\rightarrow$ **Hygiene** (`H`).
  - Customer group `B1`/`B2` $\rightarrow$ **Medical** (`M`).
  - Jika unmapped HM $\rightarrow$ tampil di kedua sub divisi (`H / M`) dan masuk alert.

### 4. Hasil Verifikasi Komparasi (Self-Verification Loop)
- Pengujian backend query antara `DashboardController::getSalespersonDashboardData()` dengan `RptslsController::getOmzetPerDivisionData()` menghasilkan angka **100% identik** di seluruh metrik (Total, Hygiene, Medical, Industrial):
  - **Ayreen (Sep 2026)**: Dashboard 62.983,67 kg $\equiv$ Rptsls 62.983,67 kg (Diff: 0,00 kg).
  - **Nike (Sep 2026)**: Dashboard 61.282,61 kg $\equiv$ Rptsls 61.282,61 kg (Diff: 0,00 kg).
  - **Zesta (Sep 2026)**: Dashboard 109.958,58 kg $\equiv$ Rptsls 109.958,58 kg (Diff: 0,00 kg).
  - **Laras (Sep 2026)**: Dashboard 44.528,04 kg $\equiv$ Rptsls 44.528,04 kg (Diff: 0,00 kg).
  - **Gwen (Sep 2026)**: Dashboard 23.691,20 kg $\equiv$ Rptsls 23.691,20 kg (Diff: 0,00 kg).
  - **Oko (Sep 2026)**: Dashboard 0,00 kg $\equiv$ Rptsls 0,00 kg (Diff: 0,00 kg).
  - **Seluruh Salesperson (Sep 2026)**: Rptsls Grand Total = 670.807,46 kg $\equiv$ Total DB Onhand (PGI = C) = 670.807,46 kg (Diff: 0,00 kg).
  - **Agustus 2026**: Seluruh salesperson dan grand total (1.985.072,53 kg) identik 100%.
  - **Pre-Cutoff (2025)**: Berjalan normal via `rpt_ET_ZVBILLING`.

### 5. Penanganan Khusus Data UCI via Fungsi Terisolasi (`getUciSalesRecords`)
- Di SAP, transaksi customer UCI (*Itochu Indonesia* - `0001100298`) berupa penagihan faktur `ZB03` dan tidak ditarik oleh function module schedule line delivery ke `rpt_et_getonhand`.
- Agar angka penjualan UCI (`803.198,44 kg` pada Agustus 2026) tetap akurat dan tidak mengotori logic utama `getSalesRecords`:
  - Dibuat fungsi khusus terisolasi `getUciSalesRecords($year, $month)` yang mengambil data faktur aktual dari `rpt_ET_ZVBILLING` (`DISTCHD != 'Waste'`).
  - Pada mode post-cutoff, data `0001100298` dikecualikan dari `onhandRows` (mencegah duplikasi) dan digabungkan secara seamless dengan hasil `getUciSalesRecords`.
  - Hasil agregasi Omzet Agustus 2026:
    - **UCI (01-31 Aug)**: `803.198,44 kg` (100% Cocok).
    - **Total Lokal**: `2.290.816,86 kg` (100% Cocok).
    - **Total Export**: `497.454,11 kg` (100% Cocok).
    - **Total (Export + Lokal)**: `2.788.270,97 kg` (100% Cocok).
