# Registry Kode Error

Format kode: `PREFIX-NNN`. Kode melengkapi HTTP status, bukan menggantikannya.
Klien Android memproses `error.code`, bukan teks `error.message`.
Sumber kebenaran di kode: `src/Support/ErrorCodes.php` (unit test memeriksa bahwa setiap kode terdokumentasi di sini).

| Kode | HTTP | Makna | Pemicu | Tindakan klien |
|---|---|---|---|---|
| `REQ-001` | 400 | Isi permintaan tidak dapat dibaca | JSON tidak valid, atau bukan objek JSON | Perbaiki format permintaan |
| `REQ-002` | 413 | Permintaan terlalu besar | Isi JSON melebihi 1 MB | Kurangi ukuran data |
| `VAL-001` | 422 | Validasi input gagal | Field wajib kosong, bukan teks, terlalu panjang. Rincian per field ada di `error.details` | Tampilkan pesan per field |
| `AUTH-001` | 401 | Username atau kata sandi salah | Kredensial tidak cocok (pesan sengaja sama untuk username tak dikenal dan kata sandi salah) | Minta pengguna mengulang |
| `AUTH-002` | 403 | Akun tidak aktif | Kredensial benar tetapi akun atau perannya nonaktif; atau akun dinonaktifkan saat sesi masih berjalan | Beri tahu pengguna agar menghubungi administrator |
| `AUTH-003` | 401 | Token tidak ada atau tidak valid | Header `Authorization: Bearer` hilang, formatnya salah, atau token tidak dikenal | Arahkan ke layar login |
| `AUTH-004` | 401 | Sesi berakhir | Token kedaluwarsa (30 hari) atau sudah dicabut (logout) | Arahkan ke layar login |
| `ACCESS-001` | 403 | Peran tidak berhak | Pengguna terautentikasi mengakses endpoint yang bukan untuk perannya (mis. non-ADMIN ke `/admin/*`) | Sembunyikan fitur; tampilkan pesan tidak berwenang |
| `USER-001` | 404 | Pengguna tidak ditemukan | `user_id` tidak ada | Muat ulang daftar |
| `USER-002` | 409 | Username sudah dipakai | Username duplikat (tidak peka huruf besar/kecil) | Pilih username lain |
| `USER-003` | 409 | Email sudah dipakai | Email duplikat | Gunakan email lain |
| `USER-004` | 409 | Aksi pada akun sendiri ditolak | Admin menonaktifkan akunnya sendiri atau mengubah perannya sendiri | Gunakan admin lain |
| `USER-005` | 409 | Peran tidak dapat diubah | Pengguna masih terkait tim (staf anggota, ketua, atau manajer tim) | Lepaskan keterkaitan tim dahulu |
| `TEAM-001` | 404 | Tim tidak ditemukan | `team_id` tidak ada | Muat ulang daftar |
| `TEAM-002` | 422 | Ketua/manajer tidak valid | Ketua harus aktif berperan TEAM_LEADER; manajer harus aktif berperan SALES_MANAGER | Pilih pengguna yang sesuai |
| `TEAM-003` | 409 | Ketua sudah memimpin tim lain | Satu ketua hanya boleh memimpin satu tim | Pilih ketua lain |
| `TEAM-004` | 422 | Penempatan staf tidak valid | Pengguna bukan SALES_STAFF, atau tim nonaktif | Pilih staf/tim yang sesuai |
| `PROD-001` | 404 | Produk tidak ditemukan | `product_id` tidak ada, atau produk nonaktif bagi non-admin | Muat ulang daftar |
| `PROD-002` | 409 | Kode produk sudah dipakai | `product_code` duplikat | Gunakan kode lain |
| `CUST-001` | 404 | Customer tidak ditemukan | `customer_id` tidak ada | Muat ulang |
| `CUST-002` | 409 | Nomor telepon sudah terdaftar | Nomor (setelah dinormalisasi) sudah dimiliki customer lain. `error.details` memuat `customer_id`, `full_name`, `phone` milik customer yang ada | Tawarkan membuka customer yang ada (`GET /customers/{id}`) |
| `VISIT-001` | 404 | Kunjungan tidak ditemukan | ID tidak ada, atau di luar cakupan data pengguna (sengaja tidak dibedakan) | Muat ulang |
| `VISIT-002` | 409 | Kunjungan yang sama sudah tersimpan | Kiriman ulang dengan staf, customer, waktu kunjungan, dan hasil yang identik. `error.details.visit_id` memuat ID yang sudah ada | Anggap sudah tersimpan; pakai `visit_id` tersebut |
| `PHOTO-001` | 404 | Foto tidak ditemukan | ID tidak ada atau di luar cakupan data | Muat ulang |
| `PHOTO-002` | 413 | Foto terlalu besar | Berkas > 2 MB (A-11) atau melebihi batas PHP | Kompres di perangkat |
| `PHOTO-003` | 409 | Batas foto tercapai | Sudah ada 5 foto (tidak termasuk yang berstatus FAILED) pada kunjungan | Jangan unggah lagi |
| `PHOTO-004` | 415 | Bukan JPEG valid | Isi berkas bukan JPEG (diperiksa dari isi, bukan nama/ekstensi) | Kirim JPEG |
| `PHOTO-005` | 500 | Foto gagal disimpan | Server tidak dapat menulis berkas, atau unggahan terpotong. Baris foto ditandai `FAILED`; `error.details.photo_id` memuat ID untuk coba ulang | Ulangi dengan `POST /photos/{id}/file` |
| `PHOTO-006` | 409 | Foto sudah berhasil | Coba ulang pada foto berstatus `SUCCESS` | Abaikan |
| `TRX-001` | 404 | Transaksi tidak ditemukan | ID tidak ada, atau di luar cakupan data pengguna (sengaja tidak dibedakan) | Muat ulang |
| `TRX-002` | 422 | Produk tidak valid | Produk tidak ada atau nonaktif pada item baru/yang diganti (FR-008). `error.details` menunjuk `items.N.product_id` | Pilih produk aktif |
| `TRX-003` | 409 | Transaksi sama sudah tersimpan | Staf, customer, dan isi item identik dengan transaksi non-batal dalam 10 menit terakhir. `error.details` memuat `transaction_id` dan `transaction_code` | Anggap sudah tersimpan |
| `TRX-004` | 409 | Status tidak mengizinkan tindakan | Ubah/kirim ulang selain pada REJECTED; setujui/tolak selain pada WAITING_APPROVAL; batalkan selain pada APPROVED. `error.details.status` memuat status saat ini | Muat ulang transaksi |
| `ROUTE-001` | 404 | Endpoint tidak ditemukan | Path tidak terdaftar | Periksa URL |
| `ROUTE-002` | 405 | Metode HTTP tidak didukung | Path ada tetapi metode berbeda; header `Allow` menyebut metode yang didukung | Periksa metode |
| `DB-001` | 503 | Basis data tidak tersedia | Koneksi database gagal | Coba lagi nanti |
| `SYS-001` | 500 | Kesalahan internal server | Exception yang tidak tertangani. Detail hanya ada di `storage/logs`, tidak dikirim ke klien | Coba lagi; laporkan bila berulang |

Kode untuk modul berikutnya (mis. `CUSTOMER-xxx`, `TRX-xxx`) ditambahkan pada tahap yang bersangkutan.
