# ALUR ANTRIAN LAMARAN (queued → submitted) — v2

> Keputusan: antrian DIISI OLEH BE. Agent HANYA mengambil (pick) & mengubah status (update).
> Dokumen ini jadi acuan implementasi BE + agent (PROMPT-BE-GOLANG §8, skill §4).

## Status aplikasi

```
queued (antrian — diisi BE, TIDAK memakai kuota)
  ↓  (agent proses)
submitted (sukses dilamar — SATU-SATUNYA yang memakai kuota)
failed   (SUDAH dicoba melamar tapi gagal: teknis/pretest)
skipped  (TIDAK dilamar — dilewati: tidak cocok profile, portal expired, external redirect)
duplicate (url sudah pernah dilamar)
```

## Fase 0 — Pengisian antrian (BE, di luar agent)

- Sumber: import/admin/backend (priority dari chat sudah DIHAPUS — antrian murni tabel `applications`).
- `POST /v1/agent/applications` status=`queued` → TANPA cek kuota.
- Anti-duplikat saat insert: `(user_id, job_url)` sudah ada → 409/skip.

## Fase 1 — Daily run (agent, dipicu scheduler BE via chat proxy)

```
1. sync-profile            → preferensi + data CV terbaru
2. check-quota             → baca SISA kuota (habis? → lapor → selesai)
3. verifikasi portal       → connected / expired
4. PICK sebanyak sisa kuota → GET /v1/agent/applications?status=queued&random=1&limit=<sisa>
                             (BE clamp limit = min(limit, sisa kuota))
                             antrian kosong → lapor → daily report
5. FILTER per loker        → portal expired → SKIP
                           → url sudah submitted → update duplicate → SKIP
                           (banyak skip & kuota sisa → PICK lagi)
6. LAMAR per loker (1 tab) → butuh login → ask_user → tunggu
                           → pretest/pertanyaan form → JAWAB DARI DATA CV
                             (data tidak ada di CV → failed)
7. UPDATE tiap selesai     → PATCH status: submitted / failed / duplicate
                             (by id ATAU by url+user_id)
                             403 quota_exceeded → berhenti (sisa antrian tetap queued)
8. Ulangi 4–7               → s.d. kuota habis / antrian kosong / semua ke-skip
9. daily report             → SELALU dikirim (jujur, walau 0)
```

## Fase 2 — Update status (BE yang memutuskan)

- `PATCH /v1/agent/applications/status`
  - **Payload wajib**: `{"id": <applications.id — uuid>, "status": "submitted"|"duplicate"|"failed"}`
  - (Alternatif opsional: `{"user_id", "job_url", "status"}` bila id tidak tersedia)
  - **Validasi**: hanya 3 nilai itu; selain itu (termasuk `queued`) → 400/422 invalid_status
  - → `submitted`: cek kuota DULU (transaksi, 403 TANPA update) + `applied_at = now`
  - → `duplicate` / `failed`: tanpa cek kuota
- 200 `{"updated":true,"id"}` · 404 baris tidak ada · 403 kuota penuh.

## Aturan kunci

| Aturan | Detail |
|---|---|
| Kuota | Hanya `submitted` memakai jatah (queued gratis) — 100% dihitung BE dari `user_service` |
| Mesin | Tidak insert, tidak hapus — hanya pick & update |
| Limit pick | = sisa kuota (dari `GET /v1/agent/quota`), BE clamp defensif |
| **selected_portal** | Hanya portal di whitelist BE yang boleh diproses (agent tolak di luar daftar) |
| Anti-duplikat | 2 lapis: saat insert (BE) + sebelum lamar (agent) |
| Pretest | Dijawab dari data CV yang di-upload; data tidak ada → failed |
| Portal expired | SKIP (tidak dilamar) |
| External redirect | SKIP → failed (hanya lamar di form internal portal) |
| Satu tab | Navigasi, bukan tab baru |
| Header | `X-Internal-Token` (agent → BE) |

## Edge cases

| Kasus | Keputusan |
|---|---|
| Antrian kosong | Lapor → daily report (0 lamaran) |
| Kuota habis | Berhenti (sisa antrian TETAP queued) |
| Pretest butuh jawaban | Jawab dari CV; data tidak ada → failed |
| Portal expired saat pick | Skip loker portal itu |
| Gagal teknis | Retry maks 3× → tetap gagal → failed |
| Duplikat (sudah submitted) | update duplicate → skip → pick lain |
