﻿# Dokumentasi Final Fitur Estimate Activity History (Full Snapshot)

## 1. Ringkasan
Dokumen ini adalah spesifikasi final implementasi histori aktivitas untuk modul **Estimate**.
Pendekatan yang disepakati: **full snapshot per event** (baik saat ganti status aktivitas maupun saat edit data).

> Koreksi penting (2026-04-16): untuk modul **Estimate**, activity code mengikuti status dokumen estimate, yaitu `DRAFT`, `SENT`, `ACCEPTED`, `DECLINED`.  
> Daftar `NEW/QUALIFIED/NEGOTIATION/...` adalah flow **Lead**, bukan flow Estimate.

Tujuan utama:
1. Jejak perubahan estimate terbaca dari awal sampai final.
2. Revisi per aktivitas bisa dihitung jelas.
3. Audit trail lengkap (siapa, kapan, perubahan apa).
4. UX mudah dibaca melalui timeline di halaman detail estimate.
5. Integritas data terjaga dengan mekanisme append-only + hash snapshot.

## 2. Tujuan Bisnis
1. Mengetahui progres estimate per aktivitas (`NEW` s.d. `RENEWAL`) secara historis.
2. Mengetahui berapa kali revisi pada tiap aktivitas.
3. Menyediakan bukti audit lengkap untuk kebutuhan kontrol internal.
4. Menurunkan risiko data hilang/tertumpuk akibat edit berulang.

## 3. Scope
### In Scope
1. Tabel histori aktivitas estimate.
2. Aturan versioning, revisi, dan transisi aktivitas.
3. Penyimpanan full snapshot semua data estimate.
4. Timeline UX di panel kanan halaman detail estimate.
5. Skenario UAT fungsional, non-fungsional, dan UX.

### Out of Scope
1. Perubahan besar struktur inti tabel `estimates`.
2. Perubahan workflow modul non-estimate (sales order, invoicing, procurement).
3. Otomasi AI scoring/next-best-action.

## 4. Asumsi Teknis
1. DB aktif: `MySQLi` (MariaDB), prefix tabel aplikasi: `rise_`.
2. Tabel inti existing tetap dipakai: `rise_estimates`, `rise_estimate_items`, `rise_clients`, dll.
3. Histori baru bersifat additive (menambah tabel), bukan mengganti mekanisme existing.

## 5. Data Model Final

### 5.1 Daftar Aktivitas (Process Activity)
Aktivitas proses estimate yang dicatat:
1. `NEW`
2. `QUALIFIED`
3. `NEGOTIATION`
4. `FOLLOWUP_1`
5. `FOLLOWUP_2`
6. `NO_RESPONSE`
7. `WON`
8. `LOST`
9. `RENEWAL`

Catatan:
1. Ini berbeda dari status dokumen estimate (`draft/sent/accepted/declined`).
2. Status dokumen tetap dipertahankan pada tabel `rise_estimates`.

### 5.2 DDL - Master Aktivitas (Opsional tapi Direkomendasikan)
```sql
CREATE TABLE IF NOT EXISTS rise_estimate_activity_master (
    code                VARCHAR(30)  NOT NULL,
    name                VARCHAR(100) NOT NULL,
    sort_order          INT          NOT NULL DEFAULT 0,
    is_terminal         TINYINT(1)   NOT NULL DEFAULT 0,
    is_active           TINYINT(1)   NOT NULL DEFAULT 1,
    created_at          DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at          DATETIME     NULL,
    PRIMARY KEY (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

Seed minimum:
1. `NEW`, `QUALIFIED`, `NEGOTIATION`, `FOLLOWUP_1`, `FOLLOWUP_2`, `NO_RESPONSE`, `WON`, `LOST`, `RENEWAL`
2. Tandai `is_terminal=1` untuk `WON` dan `LOST`.

### 5.3 DDL - Tabel Histori Event + Full Snapshot
```sql
CREATE TABLE IF NOT EXISTS rise_estimate_activity_history (
    id                      BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    estimate_id             INT UNSIGNED    NOT NULL,
    event_type              ENUM('STATUS_CHANGE','EDIT','SYSTEM_SYNC') NOT NULL,
    activity_code           VARCHAR(30)     NOT NULL,
    cycle_no                INT UNSIGNED    NOT NULL DEFAULT 1,
    activity_entry_no       INT UNSIGNED    NOT NULL DEFAULT 1,
    revision_no             INT UNSIGNED    NOT NULL DEFAULT 1,
    version_no              INT UNSIGNED    NOT NULL,
    estimate_doc_status     VARCHAR(30)     NOT NULL,
    change_note             TEXT            NULL,
    snapshot_hash           CHAR(64)        NOT NULL,
    snapshot_json           LONGTEXT        NOT NULL,
    changed_by              INT UNSIGNED    NOT NULL,
    changed_at              DATETIME        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    ip_address              VARCHAR(45)     NULL,
    user_agent              VARCHAR(255)    NULL,
    PRIMARY KEY (id),
    UNIQUE KEY uq_estimate_version (estimate_id, version_no),
    KEY idx_estimate_snapshot_hash (estimate_id, snapshot_hash),
    KEY idx_estimate_changed_at (estimate_id, changed_at),
    KEY idx_estimate_activity (estimate_id, activity_code, changed_at),
    KEY idx_event_type_changed_at (event_type, changed_at),
    CONSTRAINT fk_eah_estimate FOREIGN KEY (estimate_id) REFERENCES rise_estimates(id),
    CONSTRAINT fk_eah_user FOREIGN KEY (changed_by) REFERENCES rise_users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

Catatan kompatibilitas MariaDB:
1. Gunakan `LONGTEXT` untuk `snapshot_json` agar aman lintas versi.
2. Validasi JSON dilakukan di layer aplikasi (`json_encode/json_decode`) sebelum simpan.
3. `snapshot_hash` wajib diisi dari `SHA-256(snapshot_json_canonical)`.
4. Tabel ini bersifat **append-only** (tidak boleh update/delete dari aplikasi).

### 5.4 DDL - Tabel State Cepat (Sangat Direkomendasikan untuk Performa)
```sql
CREATE TABLE IF NOT EXISTS rise_estimate_activity_state (
    estimate_id                 INT UNSIGNED NOT NULL,
    current_activity_code       VARCHAR(30)  NOT NULL,
    current_cycle_no            INT UNSIGNED NOT NULL DEFAULT 1,
    current_activity_entry_no   INT UNSIGNED NOT NULL DEFAULT 1,
    current_revision_no         INT UNSIGNED NOT NULL DEFAULT 1,
    last_version_no             INT UNSIGNED NOT NULL DEFAULT 1,
    last_changed_by             INT UNSIGNED NOT NULL,
    last_changed_at             DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (estimate_id),
    KEY idx_state_activity (current_activity_code, last_changed_at),
    CONSTRAINT fk_eas_estimate FOREIGN KEY (estimate_id) REFERENCES rise_estimates(id),
    CONSTRAINT fk_eas_user FOREIGN KEY (last_changed_by) REFERENCES rise_users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

Tujuan tabel state:
1. Query list/tabel cukup baca 1 baris per estimate.
2. Timeline detail tetap baca dari tabel history.

### 5.5 Kebijakan Keamanan & Integritas (Wajib)
1. Model histori hanya expose operasi `INSERT` + `SELECT`.
2. Endpoint `UPDATE/DELETE` untuk history tidak boleh ada.
3. Hak akses DB user aplikasi: revoke `UPDATE` dan `DELETE` pada `rise_estimate_activity_history`.
4. Verifikasi hash dilakukan saat baca detail snapshot (opsional sampling untuk performa).
5. Role-based access untuk lihat snapshot lengkap (khusus admin/supervisor/owner terkait).

### 5.6 Strategi Skalabilitas & Retensi
1. Retensi aktif: snapshot lengkap disimpan minimal 24 bulan pada tabel utama.
2. Data >24 bulan dipindah ke tabel arsip (mis. `rise_estimate_activity_history_archive`).
3. Sediakan proses arsip bulanan (off-peak) + monitoring ukuran tabel.
4. Jika perlu filter cepat dari isi JSON, gunakan **generated columns** untuk atribut yang sering dipakai (mis. `client_id`, `estimate_number`, `doc_status`).

## 6. Struktur Snapshot JSON (Wajib Lengkap)
Setiap event menyimpan full snapshot dengan struktur minimum berikut:

```json
{
  "estimate": {
    "id": 66,
    "estimate_number": "EST#0066-000",
    "status": "draft",
    "estimate_date": "2026-03-27",
    "valid_until": "2026-03-27",
    "client_id": 123,
    "owner_id": 45,
    "source_id": 10,
    "domain_id": 2,
    "top_id": 4,
    "meta_data": {}
  },
  "client": {
    "name": "Suku Dinas ...",
    "address": "Pasar Jum'at ...",
    "phone": "0217694519"
  },
  "items": [
    {
      "item_id": 1001,
      "title": "HOSE POLYESTER ...",
      "sku": "SKU-001",
      "part_no": "PN-001",
      "qty": 2880,
      "price": 156000,
      "disc_percent": 0,
      "disc_amount": 0,
      "net_price": 156000,
      "line_total": 449280000
    }
  ],
  "totals": {
    "subtotal": 449280000,
    "tax_basis": 411840000,
    "tax_total": 49420800,
    "grand_total": 498700800,
    "currency": "IDR"
  },
  "tax_rules": {
    "tax_id": 1,
    "tax_rate": 12.0,
    "tax_name": "PPN",
    "tax2_id": null,
    "tax2_rate": null,
    "tax_basis_mode": "before_tax"
  },
  "attachments": [],
  "audit": {
    "captured_at": "2026-04-15 10:00:00",
    "captured_by": 45
  }
}
```

## 7. Flow Implementasi

### 7.1 Trigger Event Wajib Simpan Snapshot
1. Saat create estimate pertama kali.
2. Saat edit header estimate.
3. Saat tambah/edit/hapus item estimate.
4. Saat update status dokumen estimate (`draft/sent/accepted/declined`).
5. Saat ganti aktivitas proses (`NEW` s.d. `RENEWAL`).

### 7.2 Urutan Proses Save Event
1. Mulai **DB transaction**.
2. Lock state estimate terkait (`FOR UPDATE`) untuk mencegah bentrok versi.
3. Baca state terakhir pada `rise_estimate_activity_state`.
4. Ambil data sebelum/sesudah perubahan dan hitung apakah ada perubahan material.
5. Jika tidak ada perubahan material (no-op save), akhiri tanpa membuat history baru.
6. Tentukan `event_type` (`STATUS_CHANGE` atau `EDIT`).
7. Hitung `version_no`, `cycle_no`, `activity_entry_no`, `revision_no`.
8. Bangun full snapshot (header + client + items + totals + tax_rules + lampiran + meta).
9. Hitung `snapshot_hash = SHA-256(snapshot_json_canonical)`.
10. Simpan ke `rise_estimate_activity_history`.
11. Update `rise_estimate_activity_state`.
12. Simpan perubahan pada `rise_estimates` (jika ada perubahan data inti) di transaksi yang sama.
13. Commit transaksi. Jika salah satu langkah gagal, rollback semuanya.

### 7.3 Aturan Penomoran
1. `version_no` naik +1 setiap event valid.
2. `revision_no`:
   - Reset ke `1` saat `STATUS_CHANGE`.
   - Naik +1 saat `EDIT` di aktivitas yang sama.
3. `activity_entry_no`:
   - Naik +1 jika kembali masuk aktivitas yang pernah dilewati di cycle yang sama.
4. `cycle_no`:
   - Naik +1 saat `RENEWAL` memulai siklus baru.

## 8. Rule Validasi Final

### 8.1 Rule Umum
1. Semua event wajib menyimpan `snapshot_json`.
2. `estimate_id` wajib valid.
3. `activity_code` wajib salah satu dari master aktivitas.
4. `changed_by` wajib user aktif.
5. `version_no` tidak boleh duplikat per `estimate_id`.
6. `snapshot_hash` wajib sesuai hash dari `snapshot_json`.

### 8.2 Rule Aktivitas
1. `NO_RESPONSE`, `LOST`, `WON` wajib isi `change_note`.
2. `WON` hanya boleh jika status dokumen estimate minimal `accepted`.
3. `RENEWAL` hanya boleh setelah status terminal (`WON` atau `LOST`) pada cycle aktif.

### 8.3 Rule Transisi Aktivitas
Transisi yang diizinkan:
1. `NEW -> QUALIFIED | NO_RESPONSE | LOST`
2. `QUALIFIED -> NEGOTIATION | FOLLOWUP_1 | NO_RESPONSE | LOST`
3. `NEGOTIATION -> FOLLOWUP_1 | FOLLOWUP_2 | WON | LOST | NO_RESPONSE`
4. `FOLLOWUP_1 -> FOLLOWUP_2 | NEGOTIATION | WON | LOST | NO_RESPONSE`
5. `FOLLOWUP_2 -> NEGOTIATION | WON | LOST | NO_RESPONSE`
6. `NO_RESPONSE -> FOLLOWUP_1 | LOST`
7. `WON -> RENEWAL`
8. `LOST -> RENEWAL` (opsional, sesuai kebijakan bisnis)

### 8.4 Rule Keamanan Akses Snapshot
1. Snapshot full hanya bisa diakses role: `admin`, `supervisor`, `owner estimate`.
2. User lain hanya melihat ringkasan timeline (tanpa detail sensitif).
3. Akses compare versi mengikuti rule akses snapshot.

## 9. Wireframe Tekstual (Tanpa Coding)

Lokasi: halaman detail estimate (`/estimates/view/{id}`), panel kanan kosong seperti screenshot.

```text
+---------------------------------------------------------------+
| [Konten utama estimate: header + tabel item + total]         |
+------------------------------------------+--------------------+
                                           | Timeline Aktivitas |
                                           |--------------------|
                                           | [Filter]           |
                                           | (Semua) (Status)   |
                                           | (Edit) (Aktivitas) |
                                           |--------------------|
                                           | 15 Apr 10:34       |
                                           | NEGOTIATION        |
                                           | Rev 3 | Ver 12     |
                                           | by Sujanto         |
                                           | [Lihat] [Banding]  |
                                           |--------------------|
                                           | 14 Apr 16:02       |
                                           | FOLLOWUP_1         |
                                           | Rev 1 | Ver 11     |
                                           | by Sujanto         |
                                           | [Lihat] [Banding]  |
                                           |--------------------|
                                           | ...                |
                                           | [Load more]        |
                                           +--------------------+
```

### 9.1 Interaksi UX
1. Klik `Lihat` membuka drawer/modal detail snapshot.
2. Klik `Banding` membuka compare versi saat ini vs versi sebelumnya.
3. Mode compare menampilkan **diff highlighter** untuk field yang berubah (header, item, total, pajak, lampiran).
4. Default tampil 20 event terbaru.
5. Scroll menggunakan pagination bertahap (`Load more`), bukan render semua.

### 9.2 Informasi Ringkas per Baris Timeline
1. Tanggal & jam event.
2. Activity code.
3. `Rev` dan `Ver`.
4. Nama user.
5. Ikon tipe event (`STATUS_CHANGE` / `EDIT`).

## 10. Skenario UAT Fungsional

| ID | Skenario | Langkah Uji | Hasil Diharapkan |
|---|---|---|---|
| F-01 | Create estimate baru | Buat estimate baru | History dibuat `Ver 1`, `Rev 1`, `activity=NEW` |
| F-02 | Edit header estimate | Ubah perihal/alamat | History baru terbentuk, `Ver +1`, `Rev +1` (jika aktivitas sama) |
| F-03 | Tambah item | Tambah 1 produk | Snapshot baru memuat item tambahan |
| F-04 | Edit item | Ubah qty/harga | Snapshot baru berisi nilai item terbaru |
| F-05 | Hapus item | Hapus 1 item | Snapshot baru tidak lagi memuat item terhapus |
| F-06 | Ubah aktivitas | `NEW -> QUALIFIED` | History `STATUS_CHANGE`, `Rev reset 1` |
| F-07 | Edit di aktivitas baru | Edit setelah `QUALIFIED` | `Rev` naik ke 2 pada `QUALIFIED` |
| F-08 | NO_RESPONSE tanpa catatan | Simpan tanpa note | Ditolak validasi |
| F-09 | WON saat status doc draft | Set `activity=WON` | Ditolak validasi |
| F-10 | WON saat status doc accepted | Set status doc accepted lalu WON | Berhasil, event tercatat |
| F-11 | RENEWAL sebelum terminal | Set renewal tanpa WON/LOST | Ditolak validasi |
| F-12 | RENEWAL setelah WON | Set renewal | `cycle_no` naik +1 |
| F-13 | Kembali ke aktivitas lama | `NEGOTIATION -> FOLLOWUP_1 -> NEGOTIATION` | `activity_entry_no` NEGOTIATION naik |
| F-14 | Integrity versi | Simpan event paralel | Tidak ada duplikat `(estimate_id, version_no)` |
| F-15 | Lihat timeline | Buka detail estimate | Timeline tampil urut terbaru |
| F-16 | Lihat snapshot | Klik `Lihat` pada event | Detail snapshot terbuka lengkap |
| F-17 | Compare versi | Klik `Banding` | Perbedaan field/item ditandai highlight |
| F-18 | Filter timeline | Pilih filter `EDIT` | Hanya event edit tampil |
| F-19 | No-op save | Klik save tanpa perubahan data | Tidak membuat `version_no` baru |
| F-20 | Append-only enforcement | Coba update/delete history dari API | Ditolak (403/405) |
| F-21 | Hash verify | Validasi ulang hash snapshot | Nilai hash cocok |

## 11. Skenario UAT Non-Fungsional

| ID | Skenario | Target |
|---|---|---|
| N-01 | Load detail estimate dengan 5.000 history | First paint timeline < 2 detik (20 data pertama) |
| N-02 | Load more history | < 1 detik per 20 data |
| N-03 | Simpan event dengan 200 item estimate | Commit < 2 detik |
| N-04 | Concurrency 2 user edit bersamaan | Tidak ada corrupt version |
| N-05 | Snapshot JSON valid | 100% event valid JSON |
| N-06 | Storage growth | Monitoring size tabel history per bulan tersedia |
| N-07 | Verifikasi hash periodik | 100% sampel valid hash |
| N-08 | Query arsip | Query data >24 bulan tetap dapat diakses <= 3 detik |

## 12. Checklist UAT UX

### 12.1 Keterbacaan Timeline
- [ ] Urutan event jelas (terbaru di atas).
- [ ] Label `Activity`, `Rev`, `Ver`, `User`, `Waktu` mudah dipindai.
- [ ] Perbedaan `STATUS_CHANGE` vs `EDIT` terlihat jelas (ikon/warna).

### 12.2 Navigasi dan Aksi
- [ ] Tombol `Lihat` selalu membuka snapshot yang benar.
- [ ] Tombol `Banding` selalu membandingkan versi yang tepat.
- [ ] Filter tidak menghilangkan konteks pengguna.

### 12.3 Performa UX
- [ ] Timeline tidak freeze saat history banyak.
- [ ] `Load more` berjalan konsisten.
- [ ] Modal/drawer snapshot terbuka cepat.

### 12.4 Konsistensi Data
- [ ] Data di timeline konsisten dengan data riwayat DB.
- [ ] `Rev` dan `Ver` konsisten dengan aturan bisnis.
- [ ] Snapshot mencakup header + item + total + lampiran + metadata.
- [ ] Snapshot menyimpan aturan pajak (`tax_rules`) sesuai momen transaksi.

### 12.5 Error Handling UX
- [ ] Pesan validasi untuk `WON/LOST/NO_RESPONSE` jelas dan actionable.
- [ ] Jika gagal simpan history, user mendapat notifikasi yang jelas.
- [ ] Tidak ada silent failure.

### 12.6 Diff & Akses
- [ ] Diff highlighter menandai field berubah dengan benar.
- [ ] User tanpa izin tidak bisa membuka snapshot full.
- [ ] User berizin bisa melihat snapshot dan compare tanpa error.

## 13. Rencana Rollout
1. Deploy struktur tabel baru (master + history + state).
2. Terapkan kebijakan append-only pada layer API/model dan privilege DB.
3. Backfill awal dari estimate existing ke event `SYSTEM_SYNC` (`Ver 1`) per estimate.
4. Aktifkan logging snapshot untuk semua event baru.
5. Aktifkan panel timeline read-only + compare dengan diff highlighter.
6. Aktifkan retensi 24 bulan + proses archive bulanan.
7. UAT bertahap: internal admin -> sales leader -> seluruh user.

## 14. Definisi Done (DoD)
1. Semua event estimate menghasilkan history snapshot valid.
2. `snapshot_hash` terisi dan tervalidasi.
3. History benar-benar append-only dari sisi API dan DB permission.
4. Timeline tampil stabil pada data besar.
5. Rule validasi aktivitas berjalan sesuai dokumen.
6. UAT fungsional dan UX lulus minimal 95% test case.
7. Tidak ada regresi pada alur estimate existing.
