# 📘 MATERI PEMBELAJARAN (SINAU) ARSITEKTUR REFAKTOR TRANSAKSI ERP CI3
**Topik Utama:** Stateless Preprocessors, ACID Transaction Lifecycle, Exception Propagation, & DTO Contract Standardization  
**Standar Kepatuhan:** ISO 27001 (Keamanan & Audit Trail), ISO/IEC 25010 (Keandalan Sistem), ISA 315 / ISA 240 (Standar Audit Keuangan), & PSAK 72 / IFRS 15 (Pengakuan Pendapatan & Aset)

---

## DAFTAR ISI
1. [Latar Belakang & Anatomi Masalah Warisan (Legacy Debt)](#1-latar-belakang--anatomi-masalah-warisan-legacy-debt)
2. [Pilar 1: Refaktorisasi Stateless Serial Extractor](#2-pilar-1-refaktorisasi-stateless-serial-extractor)
3. [Pilar 2: Siklus Transaksi ACID & Penanganan Error di Domain Service](#3-pilar-2-siklus-transaksi-acid--penanganan-error-di-domain-service)
4. [Pilar 3: Kontrak Data DTO vs JSON String (Separation of Concerns)](#4-pilar-3-kontrak-data-dto-vs-json-string-separation-of-concerns)
5. [Matriks Komparasi Audit: Pola Lama vs Pola Standar Refaktor](#5-matriks-komparasi-audit-pola-lama-vs-pola-standar-refaktor)
6. [Cheatsheet: 5 Aturan Emas Pengembangan Modul Transaksi CI3](#6-cheatsheet-5-aturan-emas-pengembangan-modul-transaksi-ci3)

---

## 1. Latar Belakang & Anatomi Masalah Warisan (Legacy Debt)

Dalam sistem ERP berbasis CodeIgniter 3 warisan (*legacy*), terdapat tiga kebiasaan lama yang menjadi akar kerapuhan sistem:

### A. Penghentian Paksa Alur (`mati_disini` / `die()`)
*   **Praktik Lama:** Ketika data tidak valid (misal nomor seri kosong), script langsung memanggil `die($pesan);` atau `mati_disini($pesan);`.
*   **Dampak Fatal:** 
    1. **Transport Layer Membeku (*UI Freeze*):** Tombol "Simpan" mengalirkan request ke `<iframe id="result">`. Saat PHP menjalankan `die()`, browser tidak mengeksekusi penutupan modal loading (*HoldOn overlay*). Pengguna melihat animasi loading berputar selamanya dan mengira server hang.
    2. **Koneksi Menggantung:** Transaksi database yang sedang aktif tidak ditutup secara eksplisit melalui `trans_rollback()`.

### B. Ketergantungan Global State (`$_SESSION`)
*   **Praktik Lama:** Model preprocessor mengambil data produk langsung dari `$_SESSION['_TR_' . $jenisTr]`, memprosesnya, dan menulis hasilnya kembali ke `$_SESSION`.
*   **Dampak Fatal:** 
    1. **Data Lenyap Permanen:** Controller memanggil `unset($_SESSION[$cCode])` setelah transaksi selesai. Jika model preprocessor tidak mengembalikan data via *return in-memory* melainkan hanya menaruhnya di sesi, data serial number musnah saat sesi dibersihkan.
    2. **Un-testable & Non-reusable:** Model tidak dapat dipanggil oleh REST API, Background Queue Worker, maupun Unit Test CLI karena memerlukan sesi browser aktif.

---

## 2. Pilar 1: Refaktorisasi Stateless Serial Extractor

### A. Konsep Stateless & Context Injection
Pada `PreProdukSerialNumberExtractor.php` dan `PreProdukSerialNumberExtractorPaket.php`:
*   Sumber data `items` dan `items2` (atau `items6` dan `items7`) **wajib diambil dari parameter masukan in-memory** (`$inParams['payload']` atau `$inParams['items2']`).
*   Sesi global (`$_SESSION`) hanya boleh dijadikan **fallback mirror cadangan**, bukan sumber utama data.
*   Hasil ekstraksi **wajib disuntikkan ke variabel internal objek** `$this->result = array('items3_sum' => $items3_sum);` agar method `exec()` dapat mengembalikan data riil ke Pipeline Engine.

### B. Mengapa `throw new Exception` Superior Dibanding `return false`?
Sering muncul pertanyaan: *"Apakah di model preprocessor cukup mengembalikan `return false;` jika terjadi error?"*

**TIDAK BUKAN HANYA TIDAK CUKUP, TAPI SANGAT BERBAHAYA!**

```php
// Analisis di ComTransactionPipelineEngine.php:
$m = new $mdlName($resultParams);
$m->pair($masterId, $subParams); // ❌ Return value pair() TIDAK PERNAH dicek oleh engine!
$gotParams = $m->exec();
```

1.  **Kelemahan Logika (`return false`):**
    Karena pipeline engine tidak mengecek return dari `pair()`, jika model hanya me-return `false`, pipeline engine menganggap proses berhasil dan lanjut mengeksekusi baris berikutnya.
2.  **Celah Salah Saji (ISA 315 & PSAK 72):**
    Barang berharga (AC, Laptop, Alat Berat) terpotong dari persediaan dan faktur keluar, namun **nomor seri tidak pernah tercatat di database**. Saat audit fisik atau klaim garansi, perusahaan kehilangan kendali audit trail atas asetnya.
3.  **Keunggulan `throw new Exception` (Fail-Fast Principle):**
    Exception memaksa eksekusi PHP berhenti seketika di baris validasi yang gagal, mencegah eksekusi query SQL berikutnya, dan langsung melompat (*bubble up*) ke penangkap transaksi utama (`try-catch`).

---

## 3. Siklus Transaksi ACID & Penanganan Error di Domain Service

### A. Mitos `$this->db->trans_status() === false` Tanpa `try-catch`
Kode lama sering kali hanya menulis:
```php
$this->db->trans_start();
// ... proses transaksi ...
$this->db->trans_complete();

if ($this->db->trans_status() === false) {
    return array('status' => false, 'message' => 'Gagal DB');
}
```

**Kelemahan Kritis:**
Blok di atas **hanya berjalan jika query SQL pasif CI3 yang bernilai false**.  
Jika di tengah-tengah alur terdapat `throw new Exception("Nomor seri kosong")`, eksekusi PHP **langsung melompat melewati baris 397 (`trans_complete`)**. Akibatnya:
- Baris `trans_complete()` tidak pernah disentuh.
- `trans_rollback()` tidak dieksekusi secara eksplisit.
- Layar memunculkan crash oranye PHP mentah (*information disclosure*).

### B. Implementasi Pola Emas ACID CI3 (Gold Standard)
Pada `ComSalesOrderFollowupService.php` dan `ComSalesOrderService.php`, setiap transaksi wajib mengikuti struktur:

```php
// 1. Mulai Transaksi Database CI3 (Atomicity Start)
$this->db->trans_start();

try {
    // 2. Eksekusi Seluruh Pipeline Domain & Penulisan Data
    $this->executeFollowupPreProcessors(...);
    $insertID = $tr->writeMainEntries(...);
    $this->persistFollowupDetailItems(...);
    $this->postFinancialLedgerAndPaymentSources(...);

    // 3. Commit Transaksi Database (ACID Complete)
    $this->db->trans_complete();

    // 4. Verifikasi Status Eksekusi Database
    if ($this->db->trans_status() === false) {
        throw new Exception('Penyimpanan transaksi gagal pada level database. Perubahan telah dibatalkan.');
    }

    // 5. Kembalikan DTO Sukses
    return array(
        'status'       => true,
        'code'         => 'OK_FOLLOWUP_SAVED',
        'nomer'        => $tmpNomorNota,
        'transaksi_id' => $insertID,
        // data pelengkap ...
    );

} catch (Exception $e) {
    // 6. JAMINAN MUTLAK ACID: Rollback Paksa Seluruh Perubahan Database
    $this->db->trans_rollback();

    // 7. Kembalikan DTO Gagal Terstandarisasi
    return array(
        'status'  => false,
        'code'    => 'ERR_FOLLOWUP_FAILED',
        'message' => $e->getMessage()
    );
}
```

---

## 4. Kontrak Data DTO vs JSON String (Separation of Concerns)

### A. Level Service Layer (Internal PHP)
*   **Format Wajib:** **PHP Associative Array (DTO / Data Transfer Object)**.
*   **DILARANG Me-return String JSON (`json_encode`):**
    - Jika Service me-return JSON string, maka Controller (`FollowUp.php`, `Create.php`), worker CLI, maupun unit test harus memanggil `json_decode($res, true)` berulang kali. Ini adalah pemborosan CPU & memori (*performance penalty*).
    - Berisiko rusak (*silent null*) jika ada byte non-UTF8 pada database.
    - Struktur array PHP yang terstandar sudah secara alami **JSON-Ready** (dapat di-encode menjadi JSON kapan saja dibutuhkan).

### B. Level Controller & Transport Layer (Eksternal Browser)
*   **Format Wajib:** **HTML/JavaScript SweetAlert (`swalAlert`)**.
*   **DILARANG Me-return Teks JSON Mentah ke Iframe `#result`:**
    - Arsitektur web form ERP saat ini menggunakan `<iframe id="result">`.
    - Iframe tidak memiliki parser AJAX otomatis untuk JSON. Jika Controller me-return `{"status": false, ...}`, teks tersebut hanya teronggok di iframe tersembunyi, popup HoldOn tidak tertutup, dan layar freeze.
    - Controller bertugas menangkap DTO dari Service dan menerjemahkannya ke browser:
      ```php
      $result = $followupService->processSalesOrderFollowup(...);

      if ($result['status'] === false) {
          $arrAlert = array(
              "type"              => "warning",
              "title"             => "Validasi Transaksi",
              "html"              => $result['message'],
              "confirmButtonText" => "Periksa Kembali"
          );
          echo swalAlert($arrAlert);
          return;
      }
      ```

---

## 5. Matriks Komparasi Audit: Pola Lama vs Pola Standar Refaktor

| Aspek Evaluasi | Pola Lama (Legacy) | Pola Baru (Refaktor Standar) | Standar Kepatuhan |
| :--- | :--- | :--- | :--- |
| **Penyimpanan State** | Hardcoded di `$_SESSION` global | Stateless via Parameter Injection DTO | ISO/IEC 25010 (Modularitas) |
| **Penghentian Validasi** | `die()` / `mati_disini()` | `throw new Exception($msg)` | Clean Architecture (Fail-Fast) |
| **Jaminan ACID** | Hanya mengandalkan `trans_complete()` | `try-catch` + eksplisit `trans_rollback()` | ISO 27001 & ACID Compliance |
| **Respon Kegagalan** | Layar putih / oranye / freeze | DTO Terstandar $\to$ Modal SweetAlert | User Experience & Keamanan |
| **Integritas Sesi Belanja** | Sesi hilang saat error / crash | Sesi belanja utuh, user tinggal edit input | ISA 240 (Pencegahan Double Entry) |
| **Uji Kompatibilitas API** | Tidak bisa di-test tanpa browser | Reusable 100% untuk CLI, Unit Test, & API | ISO/IEC 25010 (Testability) |

---

## 6. Cheatsheet: 5 Aturan Emas Pengembangan Modul Transaksi CI3

1.  **Dilarang Keras `die()` atau `mati_disini()` di Domain Service / Model.**  
    Gunakan `throw new InvalidArgumentException(...)` untuk input salah dan `throw new Exception(...)` untuk kegagalan bisnis.
2.  **Seluruh Blok Database Finansial Wajib Dilindungi `try ... catch`.**  
    Buka dengan `$this->db->trans_start()`, bungkus dengan `try`, dan panggil `$this->db->trans_rollback()` di dalam `catch`.
3.  **Single Contract DTO Return.**  
    Service wajib selalu mengembalikan array dengan minimal key:
    - Sukses: `['status' => true, 'code' => 'OK_...', 'nomer' => ..., 'transaksi_id' => ...]`
    - Gagal: `['status' => false, 'code' => 'ERR_...', 'message' => ...]`
4.  **Hormati Arsitektur Iframe `#result`.**  
    Jangan me-return JSON mentah pada controller yang dipanggil via iframe `#result`. Gunakan `echo swalAlert($arrAlert);` agar spinner HoldOn menutup dan interaksi user kembali normal.
5.  **Audit Trail Immutability.**  
    Jangan pernah me-`update` atau me-`delete` tabel mutasi jurnal/stok finansial. Gunakan mekanisme jurnal koreksi/storno baru (*append-only*).
