# Penanganan Concurrent Requests & Idempotency

## Skenario yang Didukung

### ✅ 1. Batch Webhook (Multiple Payloads dalam 1 Request)

**Format dari Accurate:**
```json
[
  {"type": "SALES_INVOICE", "uuid": "abc-123", ...},
  {"type": "SALES_RECEIPT", "uuid": "def-456", ...}
]
```

**Penanganan:**
- ✅ Handler loop semua payload dalam array
- ✅ Setiap payload disimpan ke queue terpisah
- ✅ Semua diproses oleh worker pool

**Log yang muncul:**
```
📦 [Webhook] Batch detected: 2 payload(s) - types: [SALES_INVOICE, SALES_RECEIPT]
  → [Webhook] Queued payload 1/2: type=SALES_INVOICE, uuid=abc-123, queue_id=123
  → [Webhook] Queued payload 2/2: type=SALES_RECEIPT, uuid=def-456, queue_id=124
✅ [Webhook] Successfully queued 2 payload(s) - queue IDs: [123, 124]
```

---

### ✅ 2. Concurrent Requests (Multiple Requests Bersamaan)

**Skenario:** Accurate mengirim 2 request HTTP terpisah dengan payload yang sama dalam waktu bersamaan (double inject).

**Request 1:**
```
POST /webhook/accurate
Body: {"type": "SALES_RECEIPT", "uuid": "xyz-789", ...}
```

**Request 2 (bersamaan):**
```
POST /webhook/accurate
Body: {"type": "SALES_RECEIPT", "uuid": "xyz-789", ...}  ← Same UUID!
```

**Penanganan:**

1. **Go's HTTP Server** handle concurrent requests secara otomatis (setiap request di goroutine terpisah)
2. **Idempotency Check** menggunakan UUID dari payload:
   - Check apakah UUID sudah ada di `webhook_queue` sebelum INSERT
   - Jika sudah ada → skip (return existing queue ID)
   - Jika belum ada → INSERT ke queue

**Log yang muncul:**

**Request 1:**
```
📥 [Webhook] Incoming POST /webhook/accurate Content-Length:208
  → [Webhook] Queued payload 1/1: type=SALES_RECEIPT, uuid=xyz-789, queue_id=125
✅ [Webhook] Successfully queued 1 payload(s) - queue IDs: [125]
```

**Request 2 (bersamaan):**
```
📥 [Webhook] Incoming POST /webhook/accurate Content-Length:208
⚠️ [Webhook] Duplicate detected (UUID: xyz-789), skipping payload 1/1
✅ [Webhook] Successfully queued 1 payload(s) - queue IDs: [125]  ← Same ID!
```

**Hasil:** Hanya **1 queue entry** yang dibuat, tidak ada duplicate processing.

---

### ✅ 3. Race Condition Handling

**Skenario:** 2 request datang bersamaan dengan UUID yang sama, keduanya check "UUID belum ada" pada waktu yang sama, lalu keduanya INSERT.

**Penanganan:**

1. **Unique Index** di SQLite:
   ```sql
   CREATE UNIQUE INDEX idx_webhook_uuid ON webhook_queue(uuid) WHERE uuid IS NOT NULL
   ```

2. **Double Check** setelah INSERT error:
   - Jika INSERT gagal karena unique constraint violation
   - Check lagi apakah UUID sudah ada
   - Jika sudah ada → gunakan existing queue ID

**Log yang muncul:**
```
⚠️ [Webhook] Duplicate detected during insert (UUID: xyz-789), using existing queue_id=125
```

**Hasil:** Race condition di-handle dengan graceful, tidak ada error atau data corruption.

---

## Database Schema

**Tabel `webhook_queue` sekarang punya kolom `uuid`:**

```sql
CREATE TABLE webhook_queue (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    type TEXT NOT NULL,
    database_id INTEGER,
    uuid TEXT,  -- ← Baru: untuk idempotency
    payload TEXT NOT NULL,
    status TEXT DEFAULT 'pending',
    attempts INTEGER DEFAULT 0,
    error TEXT,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE UNIQUE INDEX idx_webhook_uuid ON webhook_queue(uuid) WHERE uuid IS NOT NULL;
```

**Migration:** Kolom `uuid` akan otomatis ditambahkan saat service start (jika belum ada).

---

## Alur Lengkap

### Skenario: Accurate Kirim Batch + Concurrent

**Request 1 (Batch):**
```json
[
  {"type": "SALES_INVOICE", "uuid": "inv-001"},
  {"type": "SALES_RECEIPT", "uuid": "rec-001"}
]
```

**Request 2 (Bersamaan, Duplicate):**
```json
[
  {"type": "SALES_INVOICE", "uuid": "inv-001"},  ← Duplicate!
  {"type": "SALES_RECEIPT", "uuid": "rec-002"}   ← New
]
```

**Hasil:**
- ✅ `inv-001` (SALES_INVOICE) → hanya 1 queue entry (duplicate di-skip)
- ✅ `rec-001` (SALES_RECEIPT) → 1 queue entry dari Request 1
- ✅ `rec-002` (SALES_RECEIPT) → 1 queue entry dari Request 2

**Total:** 3 queue entries (bukan 4), tidak ada duplicate processing.

---

## Verifikasi

**1. Cek apakah UUID tersimpan:**

```bash
sqlite3 /opt/webhook-service/data/webhook.db "
SELECT id, type, uuid, datetime(created_at) 
FROM webhook_queue 
WHERE uuid IS NOT NULL
ORDER BY id DESC
LIMIT 10;
"
```

**2. Cek duplicate (seharusnya tidak ada):**

```bash
sqlite3 /opt/webhook-service/data/webhook.db "
SELECT uuid, COUNT(*) as count
FROM webhook_queue 
WHERE uuid IS NOT NULL
GROUP BY uuid
HAVING count > 1;
"
```

**Expected:** Tidak ada hasil (semua UUID unique).

**3. Test manual duplicate:**

```bash
# Kirim request pertama
curl -X POST http://202.155.95.172:9000/webhook/accurate \
  -H "Content-Type: application/json" \
  -d '{"type":"SALES_RECEIPT","uuid":"test-123","databaseId":1293289,"data":[{"salesReceiptNo":"TEST-001"}]}'

# Kirim request kedua dengan UUID sama (bersamaan)
curl -X POST http://202.155.95.172:9000/webhook/accurate \
  -H "Content-Type: application/json" \
  -d '{"type":"SALES_RECEIPT","uuid":"test-123","databaseId":1293289,"data":[{"salesReceiptNo":"TEST-001"}]}'
```

**Expected:** Log menunjukkan "Duplicate detected" untuk request kedua.

---

## Kesimpulan

✅ **Batch webhook** → Sudah support (loop semua payload)  
✅ **Concurrent requests** → Sudah dihandle (Go's HTTP server + idempotency check)  
✅ **Race condition** → Sudah dihandle (unique index + double check)  
✅ **Duplicate prevention** → Menggunakan UUID dari Accurate  

**Semua skenario sudah dihandle dengan benar!** 🎉
