# 📐 BLUEPRINT & BAGAN ARSITEKTUR: ASYNCHRONOUS STOCK OPNAME DENGAN RABBITMQ

Dokumen ini adalah cetak biru teknis resmi untuk migrasi proses penjurnalan dan pembaruan kartu stok pada modul **Stock Opname (`1119`)** dari model *Synchronous Fat Controller* ke model *Asynchronous Message Queue* menggunakan **RabbitMQ**.

---

## 1. LATAR BELAKANG & MASALAH FAT CONTROLLER

Saat ini, pada saat transaksi Opname mencapai status **Approve 2** (`1119ro -> 1119`), controller `FollowUp.php` dipaksa menjalankan seluruh beban secara sinkron di dalam siklus HTTP request:
1. Menghitung mutasi saldo stok dan HPP untuk ribuan produk.
2. Membuka transaksi database (`$this->db->trans_start()`) yang berlangsung hingga ratusan detik.
3. Menulis baris jurnal umum (`jurnal`), buku besar (`rekening`), dan buku pembantu (`rekening_pembantu_produk`).
4. Memvalidasi neraca secara sinkron (`validateJurnal()` dan `validateAllBalances()`).

### Dampak Buruk yang Terjadi:
* **HTTP Connection Timeout (Error 504 / 502):** Nginx / Apache memutus koneksi sebelum kalkulasi selesai.
* **Risiko Deadlock (MySQL Error 1213 / 1205):** Transaksi database yang menggantung lama mengunci tabel persediaan sehingga kasir toko lain macet total.
* **Kerentanan Data Pincang:** Jika user menutup browser di tengah proses loading, sebagian data masuk dan sebagian lagi gagal.

---

## 2. BAGAN ARSITEKTUR SISTEM (TOPOLOGY MAP)

Arsitektur dibagi menjadi 3 zona terisolasi untuk memastikan respon cepat ke pengguna dan integritas data di latar belakang:

```mermaid
flowchart TB
    subgraph ZONA_1 ["ZONA 1: HTTP BROWSER - Respon Kilat Kurang Dari 1 Detik"]
        A["Petugas / Supervisor - Klik Approve 2"] -->|"HTTP POST"| B["FollowUp.php - doFollowup"]
        B --> C["Simpan Data Transaksi Dasar:<br>transaksi & transaksi_data"]
        C --> D["MdlOpnameStaging:<br>Tulis ke so_headers_staging & so_items_staging"]
        D --> E["Rabbitmq_publisher.php:<br>Publish Event ke RabbitMQ"]
        E -->|"Respon 200 OK"| F["Browser User:<br>Dokumen Disetujui & Masuk Antrean Background"]
    end

    subgraph ZONA_2 ["ZONA 2: RABBITMQ BROKER - 192.168.5.14:5672"]
        E -.->|"Pesan Persistent"| EX["Exchange Direct:<br>inventory.opname.x"]
        EX -->|"Routing Key: opname.submitted"| Q["Main Queue Durable:<br>inventory.opname.process.q"]
        Q -.->|"Jika Gagal / Exception"| DLX["DLX Exchange:<br>inventory.opname.dlx"]
        DLX --> DLQ["Dead Letter Queue:<br>inventory.opname.failed.q"]
    end

    subgraph ZONA_3 ["ZONA 3: WORKER ENGINE DAEMON - rabbit_server"]
        Q -->|"QoS Prefetch: 1 Dokumen Sekaligus"| W["OpnameWorker.php - CLI Daemon"]
        W --> G["Idempotency Check:<br>Cek apakah status sudah COMPLETED"]
        G -->|"Update Status"| H["Update so_headers_staging:<br>status = PROCESSING"]
        
        subgraph CHUNKING ["Pemrosesan Bertahap - Batch per 200 Item"]
            H --> I["Atomic DB Transaction:<br>trans_start"]
            I --> J["Row-Locking Inventory:<br>SELECT locker_stock FOR UPDATE"]
            J --> K["Posting Akuntansi & Kartu Stok:<br>1. Jurnal & Rekening GL<br>2. Rekening Pembantu Produk<br>3. Update locker_stock Mutasi<br>4. Generate Serial Number Baru"]
            K --> L["Commit DB:<br>trans_complete"]
        end

        L --> M["Finalisasi Status Dokumen:<br>1. transaksi status_next = complete<br>2. so_headers_staging status = COMPLETED<br>3. Catat ke so_audit_logs"]
        M -->|"Sukses Diproses"| ACK["Kirim basic_ack:<br>Pesan Resmi Dihapus dari Queue"]
        L -.->|"Gagal Transaksi DB"| NACK["Kirim basic_nack:<br>Pesan Otomatis Masuk ke DLQ"]
    end

    style ZONA_1 fill:#e8f4fd,stroke:#2b7bb9,stroke-width:2px;
    style ZONA_2 fill:#fcf3cf,stroke:#f39c12,stroke-width:2px;
    style ZONA_3 fill:#e8f8f5,stroke:#1abc9c,stroke-width:2px;
    style CHUNKING fill:#ffffff,stroke:#34495e,stroke-dasharray: 5 5;
```

---

## 3. DIAGRAM URUTAN WAKTU (SEQUENCE FLOW)

```mermaid
sequenceDiagram
    autonumber
    actor User as Supervisor / Staf
    participant Browser as Web Browser
    participant FollowUp as FollowUp.php (ERP)
    participant Staging as MySQL (Staging)
    participant Rabbit as RabbitMQ Broker
    participant Worker as OpnameWorker.php
    participant MainDB as MySQL (Ledger & Stok)

    Note over User,Browser: TAHAP 1: RESPON KILAT (Kurang Dari 1 Detik)
    User->>Browser: Klik tombol Approve 2
    Browser->>FollowUp: HTTP POST doFollowup
    FollowUp->>MainDB: Insert transaksi & transaksi_data
    FollowUp->>Staging: Insert so_headers_staging & so_items_staging (PENDING)
    FollowUp->>Rabbit: basic_publish (transaksi_id, booking_number)
    FollowUp-->>Browser: Respon Sukses: Dokumen masuk antrean background
    Browser-->>User: Selesai loading (Bisa langsung pindah menu)

    Note over Rabbit,Worker: TAHAP 2: DECOUPLED BACKGROUND CONSUME
    Rabbit->>Worker: Dispatch 1 Pesan (Prefetch = 1)
    Worker->>Staging: Update so_headers_staging ke PROCESSING
    
    loop Batch Per 200 Item (Mencegah OOM & Lock Hang)
        Worker->>MainDB: trans_start (Atomic DB Transaction)
        Worker->>MainDB: SELECT locker_stock FOR UPDATE (Row Locking)
        Worker->>MainDB: INSERT jurnal & rekening GL
        Worker->>MainDB: INSERT rekening_pembantu_produk
        Worker->>MainDB: UPDATE locker_stock (+ atau -)
        Worker->>MainDB: trans_complete (Commit DB)
        Worker->>Staging: Catat Log Audit Chunk Done
    end

    Worker->>MainDB: Update transaksi status_next = complete
    Worker->>Staging: Update so_headers_staging ke COMPLETED
    Worker->>Rabbit: basic_ack (Selesai sempurna)
```

---

## 4. SPESIFIKASI TOPOLOGI RABBITMQ

* **Host:** `192.168.5.14` (Port: `5672`)
* **Virtual Host:** `/inventory` (Terisolasi dari lalu lintas CRM/Sales)
* **Exchange:** `inventory.opname.x` (Tipe: `direct`, Durable)
* **Routing Key:** `opname.submitted`
* **Main Queue:** `inventory.opname.process.q` (Durable: `true`)
* **Dead Letter Exchange (DLX):** `inventory.opname.dlx` (Tipe: `direct`, Durable)
* **Dead Letter Routing Key:** `opname.failed`
* **Dead Letter Queue (DLQ):** `inventory.opname.failed.q` (Durable: `true`)
* **Quality of Service (QoS):** `prefetch_count = 1` (Worker hanya memproses 1 dokumen dalam satu waktu agar server tidak kehabisan RAM).

---

## 5. SKEMA DATABASE STAGING (`run_everest_modul`)

Ketiga tabel berikut berfungsi sebagai *Transactional Buffer* antara browser dan worker:

### 1. `so_headers_staging`
Menyimpan ringkasan dokumen opname yang diterbitkan dari browser:
* `id` : Primary Key Auto Increment
* `transaksi_id` : ID Transaksi Induk (Indexed, Unique)
* `booking_number` : Nomor booking / kode sesi transaksi
* `nomer_nota` : Nomor nota resmi opname
* `cabang_id` & `gudang_id` : Lokasi gudang fisik
* `jenis_transaksi` : Default `'1119'`
* `status` : `PENDING` -> `PROCESSING` -> `COMPLETED` / `FAILED`
* `retry_count` : Jumlah percobaan ulang jika terjadi kegagalan
* `total_items` : Jumlah item barang yang diselisihkan
* `payload_checksum` : MD5 hash untuk validasi integritas payload
* `created_at` & `updated_at` : Timestamp audit

### 2. `so_items_staging`
Menyimpan rincian data per barang:
* `id` : Primary Key
* `header_staging_id` : Relasi ke `so_headers_staging.id`
* `transaksi_id` : ID Transaksi Induk
* `produk_id` & `produk_nama` : Identitas barang
* `qty_sistem` : Stok saldo tercatat sistem
* `qty_fisik` : Hasil hitung fisik staf
* `qty_debet` : Selisih Lebih (+)
* `qty_kredit` : Selisih Kurang (-)
* `hpp_satuan` : Nilai HPP per unit saat approve
* `nilai_total` : Total nominal rupiah selisih
* `serial_numbers_json` : Rincian nomor seri (jika ada)
* `status` : `PENDING` / `PROCESSED` / `FAILED`

### 3. `so_audit_logs`
Pencatatan jejak audit kepatuhan ISO 27001:
* `id` : Primary Key
* `header_staging_id` & `transaksi_id` : Referensi transaksi
* `event_type` : `SUBMITTED`, `WORKER_START`, `CHUNK_DONE`, `COMPLETED`, `DLQ_FAILED`
* `actor_type` : `USER` atau `SYSTEM_WORKER`
* `actor_id` : ID petugas atau sistem
* `details` : Catatan kronologis peristiwa
* `created_at` : Waktu kejadian

---

## 6. LOGIKA ATOMIK & ATURAN AKUNTANSI WORKER

Saat worker mengeksekusi item di latar belakang, aturan jurnal akuntansi Everest tetap dipertahankan 100%:

1. **Selisih Lebih (+)**:
   * **Debet:** Akun `1010030030` (Persediaan Produk)
   * **Kredit:** Akun `7010150` (Laba Selisih Persediaan)
   * **Kartu Stok:** Penambahan saldo fisik pada `locker_stock` dan mutasi pada `locker_stock_mutasi`.
   * **Buku Pembantu:** Menulis record debet ke `rekening_pembantu_produk`.

2. **Selisih Kurang (-)**:
   * **Debet:** Akun `7020020` (Beban / Kerugian Selisih Persediaan)
   * **Kredit:** Akun `1010030030` (Persediaan Produk)
   * **Kartu Stok:** Pengurangan saldo fisik pada `locker_stock` dan mutasi pada `locker_stock_mutasi`.
   * **Buku Pembantu:** Menulis record kredit ke `rekening_pembantu_produk`.

3. **Pola Penguncian Baris (*Row-Level Locking*)**:
   ```sql
   SELECT id, jumlah FROM locker_stock 
   WHERE cabang_id = ? AND gudang_id = ? AND produk_id = ? AND state = 'active' 
   FOR UPDATE;
   ```
   *Mencegah tabrakan saldo jika kasir POS menjual barang yang sedang disesuaikan.*

---

## 7. TABEL PERBANDINGAN TEKNIS

| Parameter | Sebelum (Synchronous HTTP) | Sesudah (RabbitMQ Asynchronous) |
| :--- | :--- | :--- |
| **Waktu Tunggu Browser** | **30 s/d 180 detik** (Sering freeze/timeout) | **< 1 detik** (Respon instan) |
| **Beban Memori HTTP** | Rawan Out-Of-Memory (> 128MB per request) | **Sangat Ringan** (Hanya simpan staging) |
| **Risiko Deadlock (1213)** | **Tinggi Sekali** (Lock tabel puluhan detik) | **Nol / Rendah** (Chunking batch 200 item) |
| **Ketahanan Data Mati Lampu** | Rawan rusak jika browser ditutup paksa | **Aman 100%** (Queue Durable + Manual ACK) |
| **Kesiapan Skalabilitas** | Tidak bisa menangani opname multi-cabang serentak | **Sangat Siap** (Antrean otomatis terjadwal rapi) |

---

## 8. PANDUAN MENJALANKAN WORKER DAEMON

Untuk menjalankan worker di server RabbitMQ (`W:\rabbit_server`):

```bash
# Masuk ke direktori rabbit server
cd W:\rabbit_server

# Jalankan daemon worker
php index.php OpnameWorker start
```

*Daemon akan terus berjalan di background, mengambil pesan satu per satu (Prefetch = 1), dan mencatat log eksekusi ke console serta file log sistem.*
