01Informasi Dokumen
| Informasi | Nilai |
|---|---|
| Nama Fitur | Peminjaman Asset Karyawan (termasuk Pengembalian pinjaman) |
| Kode Fitur | KRY05 (prefix nomor transaksi KRY05-YYYYMMNNN, dipakai untuk peminjaman dan pengembalian) |
| Modul | Asset Management |
| Backend | Laravel Lumen ^10.0, PHP ^8.1, JWT tymon/jwt-auth ^2.3, Query Builder |
| Database | Struktur mengacu API/diagram-asset.dbml + migrations. ⚠️ Asumsi: MySQL/MariaDB |
| Mobile | Flutter (package professional_flutter_3_35), flutter_bloc, dio |
| Web | ⚠️ Belum ditemukan: tidak ada source web di workspace |
| Versi Dokumen | 1.6 |
| Tanggal Dokumentasi | 2026-10-27 |
| Dokumen terkait | Onboarding, Request/Penambahan (helper & pola bersama), Offboarding (mengembalikan pinjaman saat karyawan keluar) |
03Quick Overview
| Item | Informasi |
|---|---|
| Feature | Peminjaman & Pengembalian Asset Karyawan |
| Module | Asset Management |
| Actor | Karyawan (peminjam), Admin Asset (penyetuju & penerima pengembalian), System |
| Main Transaction | asset_trx_peminjaman_karyawan (dua jenis baris: jenis_status = peminjaman dan pengembalian) |
| Detail Transaction | asset_trx_peminjaman_karyawan_item |
| Relasi pengembalian | id_peminjaman_asal → header peminjaman |
| Asset Master | asset_data (status_asset = dipinjam selama dipinjam) |
| Log | asset_trx_log: peminjaman_karyawan (serah terima) dan pengembalian_karyawan (pengembalian) |
| Status | Peminjaman: pengajuan → dikonfirmasi → aktif → selesai, atau berhenti di ditolak (ditolak admin) / dibatalkan (dibatalkan karyawan, v1.6). Pengembalian: pengajuan → selesai |
| Approval | Admin memutuskan per asset (setujui/tolak) lalu menetapkan jadwal serah terima |
| API | 6 endpoint admin + 9 endpoint karyawan = 15 rute (jumlah rute tidak berubah di v1.6; parameter list bertambah: tab=dibatalkan, tab=riwayat, kondisi) |
| Mobile Screen | Karyawan: Peminjaman Asset (4 tab: Permintaan Peminjaman, Sedang Dipinjam, Pengembalian, Riwayat Peminjaman), Tambah Peminjaman Asset, Detail Peminjaman / Pengembalian. Admin: Peminjaman Asset (4 tab yang sama), Detail + sheet Konfirmasi Peminjaman Asset. Banner & kartu di Asset Saya |
| Prasyarat deploy (v1.6) | SQL manual Claude outputs/sql-peminjaman-status-dibatalkan.sql menambah nilai dibatalkan pada enum status_peminjaman. Jalankan sebelum API v1.6 dipakai (lihat RISK-010). DBML bertanda [NEW KRY05]; status eksekusi SQL di server tidak diverifikasi dari kode |
| Hasil tes (eksekusi sesi 2026-10-27) | API tests/Asset: 386 tes lulus (4850 assertion, sqlite). Mobile test/asset: 466 lulus, 5 gagal lama yang tidak terkait fitur (Receive action…, pemeliharaan gudang progress summary, dashboard 320px & 800px, Damaged asset replacement picker) |
Karyawan: pilih asset gudang + keperluan + tujuan + tanggal pinjam–kembali → Ajukan Peminjaman
│ asset_data → proses status: pengajuan
│ (atau Batalkan → semua asset → tersedia, status: dibatalkan)
▼
Admin: setujui / tolak tiap asset + jadwal serah terima (dalam periode pinjam)
│ asset ditolak → tersedia status: dikonfirmasi (atau ditolak bila semua ditolak)
▼
Karyawan: Terima Asset ─────────────► asset → dipinjam @karyawan + asset_trx_log status: aktif
│ (atau Batalkan → asset disetujui → tersedia, status: dibatalkan)
▼
Pengingat H-1 / terlambat (banner di aplikasi)
▼
Karyawan: Kembalikan Asset ─► header baru jenis pengembalian status: pengajuan
▼
Admin: kondisi + lokasi gudang tiap asset ─► asset → tersedia / rusak @gudang pilihan admin + asset_trx_log
pengembalian: selesai · peminjaman asal: selesai
▼
Tab Riwayat Peminjaman: peminjaman selesai + pengembaliannya tampil sebagai 1 entri
04Ringkasan Fitur
Peminjaman Asset memungkinkan karyawan meminjam asset gudang untuk jangka waktu tertentu (mis. dinas, pelatihan) dan mengembalikannya. Berbeda dengan Onboarding/Penambahan yang memindahkan asset secara tetap (terpakai), asset pinjaman berstatus dipinjam dan wajib kembali.
Alur utama:
- Pengajuan oleh karyawan: memilih satu atau lebih asset yang tersedia, keperluan (Kantor/Dinas/Pelatihan/Lainnya), tujuan, tanggal pinjam dan tanggal kembali. Asset langsung dikunci.
- Konfirmasi admin per asset: setiap asset disetujui atau ditolak (dengan alasan). Jika ada yang disetujui, admin menentukan jadwal serah terima yang harus berada dalam periode pinjam.
- Serah terima: karyawan menekan Terima Asset setelah menerima fisik asset → status Aktif, asset tercatat dipinjam karyawan. Karyawan juga bisa Batalkan selama peminjaman masih pengajuan atau dikonfirmasi (sebelum menerima); hasilnya status Dibatalkan dan asset yang dikunci kembali tersedia.
- Jatuh tempo: mulai H-1 tanggal kembali, peminjaman tampil sebagai pengingat; lewat tanggal ditandai Terlambat.
- Pengembalian: karyawan mengajukan pengembalian seluruh asset pinjaman; admin memeriksa kondisi tiap asset (Normal → kembali tersedia, Rusak → status rusak di gudang). Peminjaman asal otomatis Selesai.
- Riwayat (v1.6): tab Riwayat Peminjaman menampilkan peminjaman selesai beserta pengembaliannya sebagai satu entri (jumlah normal/rusak dan tanggal dikembalikan).
- Halaman 4 tab (v1.6): Permintaan Peminjaman, Sedang Dipinjam (chip Semua / Jatuh Tempo / Terlambat), Pengembalian, Riwayat Peminjaman.
05Tujuan Fitur
5.1 Tujuan Bisnis (diturunkan dari kode)
- Peminjaman asset tercatat dengan keperluan, tujuan, dan periode yang jelas.
- Admin mengontrol asset mana yang boleh dipinjam dan kapan diserahkan.
- Karyawan bertanggung jawab atas asset setelah menyatakan menerima secara fisik.
- Keterlambatan pengembalian terlihat oleh karyawan dan admin.
- Kondisi asset saat kembali tercatat; asset rusak tidak kembali ke stok tersedia.
5.2 Tujuan Sistem
- Menyimpan pengajuan, keputusan per asset, jadwal serah terima, dan pengembalian.
- Mengunci asset selama proses (
proses) dan selama dipinjam (dipinjam). - Menghitung status jatuh tempo (
sisa_hari,terlambat). - Mencatat
asset_trx_logsaat serah terima dan saat pengembalian. - Mengirim notifikasi di setiap perubahan status.
06Aktor
| Aktor | Role | Fungsi |
|---|---|---|
| Karyawan (peminjam) | User login (JWT) | Mengajukan peminjaman, menerima/membatalkan, mengajukan pengembalian |
| Admin Asset | asset_admin + karyawan aktif |
Menyetujui/menolak per asset, menetapkan jadwal serah terima, memeriksa kondisi pengembalian dan memilih lokasi gudang tiap asset |
| System | Backend + FCM | Validasi, lock, penomoran, update asset, log, pengingat jatuh tempo (tampilan), notifikasi |
07Hak Akses
| Action | Admin Asset | Karyawan | Keterangan |
|---|---|---|---|
| Melihat semua peminjaman & pengembalian | ✅ | ❌ | /asset/peminjaman-karyawan, /asset/pengembalian-karyawan |
| Melihat peminjaman & pengembalian sendiri | – | ✅ | /asset/karyawan/peminjaman, /asset/karyawan/pengembalian |
| Mengajukan peminjaman | – | ✅ | Untuk diri sendiri |
| Melihat riwayat peminjaman selesai + pengembaliannya | ✅ (semua karyawan di company) | ✅ (milik sendiri) | Tab riwayat dan key pengembalian pada detail |
| Membatalkan pengajuan (sebelum admin memutuskan) | ❌ | ✅ (status pengajuan) | Disimpan sebagai dibatalkan (v1.6; RISK-002 ditutup) |
| Setujui / tolak per asset | ✅ (status pengajuan) | ❌ | |
| Terima asset | ❌ | ✅ (status dikonfirmasi) | |
| Batalkan setelah disetujui | ❌ | ✅ (status dikonfirmasi) | Disimpan sebagai dibatalkan (v1.6; sebelumnya ditolak) |
| Ajukan pengembalian | ❌ | ✅ (status aktif) | Seluruh asset |
| Konfirmasi kondisi & lokasi gudang pengembalian | ✅ (status pengajuan) | ❌ | |
| Menolak pengembalian | ❌ | ❌ | ⚠️ Belum ditemukan (RISK-004) |
08Terminologi
| Istilah | Arti di sistem |
|---|---|
| Peminjaman | Baris header jenis_status = peminjaman |
| Pengembalian | Baris header jenis_status = pengembalian, merujuk id_peminjaman_asal |
| Keperluan | kantor, dinas, pelatihan, lainnya |
| Tujuan peminjaman | Teks bebas (maks. 255) |
| Periode pinjam | tgl_peminjaman s.d. tgl_dikembalikan |
| Jadwal serah terima | Tanggal + jam penyerahan fisik yang ditetapkan admin, harus di dalam periode pinjam |
| Aktif | Karyawan sudah menerima asset (status_asset = dipinjam) |
| Jatuh tempo (banner/dashboard) | Peminjaman aktif dengan tgl_dikembalikan ≤ besok (termasuk yang sudah lewat), asset masih dipegang, dan belum ada pengembalian berjalan |
| Chip Jatuh Tempo (tab Sedang Dipinjam) | Parameter kondisi=jatuh_tempo: tgl_dikembalikan hari ini sampai besok; yang sudah lewat tidak termasuk (masuk chip Terlambat). Tidak mengecualikan peminjaman yang pengembaliannya sedang diajukan |
| Dibatalkan | Status dibatalkan (v1.6): peminjaman dibatalkan karyawan saat pengajuan atau dikonfirmasi; ket_tolak = "Dibatalkan oleh karyawan." |
| Riwayat | Tab riwayat: peminjaman selesai dengan ringkasan pengembaliannya (key pengembalian) |
| Terlambat | Aktif dan hari ini > tgl_dikembalikan (chip Terlambat: kondisi=terlambat, tgl_dikembalikan < hari ini) |
sisa_hari |
Selisih hari ke tanggal kembali; negatif = terlambat |
dapat_dikembalikan |
Flag detail karyawan: aktif, semua asset masih dipegang, belum ada pengembalian berjalan |
09Business Rules
BR-001 — Peminjaman diajukan oleh karyawan untuk dirinya sendiri
id_karyawan diambil dari JWT; karyawan harus aktif → "Karyawan tidak ditemukan atau tidak aktif."
BR-002 — Data pengajuan wajib lengkap
keperluan (maks 255), tujuan_peminjaman (maks 255), tgl_pinjam, tgl_kembali, asset_ids (min 1, distinct) → pesan pada §24 API-009.
BR-003 — Tanggal pinjam tidak boleh lampau; tanggal kembali ≥ tanggal pinjam
tgl_pinjam after_or_equal:today, tgl_kembali after_or_equal:tgl_pinjam → "Tanggal pinjam tidak boleh sebelum hari ini." / "Tanggal kembali tidak boleh sebelum tanggal pinjam." Tidak ada batas maksimal durasi.
BR-004 — Asset harus tersedia di gudang
getLockedAssets: status_asset = tersedia & pemegang_asset = gudang (FOR UPDATE) → "Satu atau beberapa asset tidak tersedia untuk dipinjam."
BR-005 — Asset tidak boleh sedang di onboarding atau peminjaman lain
hasActiveOnboardingAsset → "…sedang diproses pada onboarding." hasActivePeminjamanAsset (peminjaman pengajuan/dikonfirmasi/aktif, item tidak ditolak) → "…sedang diproses pada peminjaman lain."
BR-006 — Asset langsung dikunci saat diajukan
asset_data.status_asset = proses.
BR-007 — Admin wajib memutuskan seluruh asset
Semua item harus diberi disetujui/ditolak → "Seluruh asset peminjaman wajib dikonfirmasi." Tolak wajib alasan → "Alasan penolakan asset wajib diisi."
BR-008 — Jadwal serah terima wajib jika ada asset disetujui dan harus dalam periode pinjam
→ "Tanggal serah terima wajib diisi." / "Jam serah terima wajib diisi." / "Tanggal serah terima harus berada dalam periode peminjaman."
BR-009 — Hasil keputusan admin
Minimal satu disetujui → dikonfirmasi; semua ditolak → ditolak dengan ket_tolak = "Seluruh asset peminjaman ditolak." Asset ditolak kembali tersedia di gudang; asset disetujui tetap proses.
BR-010 — Konfirmasi admin hanya untuk status pengajuan dan bukan milik offboarding
→ "Peminjaman asset sudah tidak menunggu konfirmasi." / "Pengembalian ini diproses melalui offboarding."
BR-011 — Terima asset hanya saat dikonfirmasi
Karyawan aktif, status dikonfirmasi, asset disetujui masih proses di gudang → asset dipinjam @karyawan, status aktif, asset_trx_log → "Peminjaman asset belum dapat diterima." / "Satu atau beberapa asset belum siap diserahterimakan."
BR-012 — Batalkan hanya saat pengajuan atau dikonfirmasi (v1.6)
Karyawan pemilik dapat membatalkan peminjaman berstatus pengajuan atau dikonfirmasi. Dari pengajuan seluruh asset item (belum diputuskan, status_item NULL) dilepas; dari dikonfirmasi hanya item disetujui (item ditolak sudah dilepas saat konfirmasi). Hanya asset yang masih proses + gudang yang diubah menjadi tersedia @gudang (asset lain tidak disentuh). Header menjadi dibatalkan (bukan lagi ditolak) dengan ket_tolak = "Dibatalkan oleh karyawan.". Tidak ada asset_trx_log karena asset tidak pernah berpindah pemegang. Status lain (aktif, selesai, ditolak, dibatalkan) → "Peminjaman asset sudah tidak dapat dibatalkan."; milik karyawan lain atau baris pengembalian → "Peminjaman asset tidak ditemukan."; karyawan nonaktif → "Karyawan tidak ditemukan atau tidak aktif."
BR-013 — Pengembalian hanya untuk peminjaman aktif, seluruh asset
Semua asset disetujui harus masih dipinjam oleh karyawan; tidak boleh ada pengembalian berjalan; tidak sedang di offboarding → "Hanya peminjaman aktif yang dapat dikembalikan." / "Satu atau beberapa asset tidak sedang dipinjam oleh karyawan." / "Pengembalian asset sedang menunggu konfirmasi." / "Asset sedang diproses pada offboarding."
BR-014 — Admin wajib mengisi kondisi dan lokasi gudang setiap asset yang dikembalikan
normal/rusak; rusak wajib keterangan → "Seluruh kondisi asset pengembalian wajib diisi." / "Keterangan kerusakan asset wajib diisi." (v1.1) Setiap item juga wajib punya id_asset_lokasi_m bertipe gudang pada company yang sama, tepat satu kali per item → "Lokasi gudang tujuan setiap asset wajib dipilih." / "Lokasi gudang tujuan tidak valid." / "Lokasi gudang tujuan asset tidak sesuai."
BR-015 — Dampak pengembalian
Normal → tersedia @gudang; Rusak → rusak @gudang + ket_rusak; keduanya di gudang pilihan admin (asset_data.id_asset_lokasi_m, item id_asset_lokasi_m_tujuan). Pengembalian selesai; peminjaman asal selesai bila tidak ada asset pinjaman yang masih dipegang.
BR-015a — Lokasi dikosongkan saat asset keluar gudang (v1.1)
Saat karyawan menerima asset pinjaman (Terima Asset), asset_data.id_asset_lokasi_m di-set NULL (kolom FK ke asset_lokasi_m; nilai '' ditolak database) karena asset tidak lagi berada di gudang.
BR-016 — Pengingat jatuh tempo
Peminjaman aktif dengan tgl_dikembalikan ≤ besok, asset masih dipegang, dan belum ada pengembalian pengajuan/dikonfirmasi → tampil di Asset Saya (karyawan) dan ringkasan admin. ⚠️ Belum ditemukan: scheduler/push otomatis.
BR-017 — Pengembalian lewat offboarding dipisahkan
Pengembalian yang dibuat oleh proses offboarding (tercatat di asset_trx_offboarding_relasi) disembunyikan dari tab, list, dan detail Pengembalian serta tidak dapat dikonfirmasi lewat menu peminjaman. Keputusan produk (v1.6): pengembalian dari offboarding sengaja tetap tampil di tab Riwayat dan pada bagian pengembalian di detail peminjaman, sehingga satu peminjaman selesai selalu menampilkan seluruh pengembaliannya.
BR-019 — Filter kondisi tab Sedang Dipinjam (v1.6)
Parameter opsional kondisi (semua | jatuh_tempo | terlambat) pada list peminjaman karyawan dan admin hanya efektif bila tab=aktif; di tab lain diabaikan. terlambat: tgl_dikembalikan < hari ini. jatuh_tempo: tgl_dikembalikan hari ini sampai besok (yang sudah terlambat tidak masuk). semua/kosong = semua peminjaman aktif. Filter diterapkan di SQL sebelum paginasi. Nilai lain → 422 "Filter kondisi peminjaman asset tidak valid." Tanpa tab/limit/offset list kembali ke respons lama non-paginasi dan kondisi diabaikan.
BR-020 — Tab Riwayat menggabungkan peminjaman dan pengembalian (v1.6)
tab=riwayat hanya berisi peminjaman selesai, urut tgl_update desc lalu id desc, paginasi sama dengan tab lain. Tiap item memuat pengembalian[] (hanya pengembalian selesai milik peminjaman dan karyawan yang sama, satu query per halaman). Detail peminjaman memuat pengembalian[] untuk semua status pengembalian (urut tgl_input asc). riwayat sengaja tidak ada di filters statis.
BR-018 — Penomoran
ID header/item id_company + YYYYMM + 3 digit; no_trx = KRY05-YYYYMMNNN (sequence yang sama untuk peminjaman & pengembalian).
10Business Flow
1. Karyawan: Asset Saya → Peminjaman → (+) Tambah Peminjaman Asset
→ Pilih Asset (Tambah Asset) → Pilih Keperluan → Tujuan Peminjaman → Tanggal Pinjam–Kembali
→ Ajukan Peminjaman [pengajuan · asset proses · notif admin]
2. Admin: Asset Karyawan → Peminjaman → tab Permintaan Peminjaman (chip Pengajuan) → Detail
→ Setujui / Tolak tiap asset (+ alasan) → Konfirmasi
→ Sheet "Konfirmasi Peminjaman Asset": Jadwal Serah Terima + Jam + Catatan → Submit
[dikonfirmasi / ditolak · notif karyawan]
3. Karyawan: banner / Detail → "Siap untuk meminjam Asset?"
→ Terima Asset [aktif · asset dipinjam · asset_trx_log · notif admin]
→ Batalkan [dibatalkan "Dibatalkan oleh karyawan." · asset tersedia · notif admin]
(Batalkan juga tersedia sejak status pengajuan, sebelum admin memutuskan)
4. H-1 tanggal kembali → pengingat jatuh tempo; lewat tanggal → "Terlambat"
5. Karyawan: Detail peminjaman aktif → Kembalikan Asset
[header pengembalian: pengajuan · notif admin]
6. Admin: Peminjaman → tab Pengembalian → Detail → Normal / Rusak tiap asset → Konfirmasi
[pengembalian selesai · asset tersedia/rusak · asset_trx_log
· peminjaman asal selesai · notif karyawan]
7. Riwayat: tab Riwayat Peminjaman menampilkan peminjaman selesai + ringkasan pengembaliannya
11Flow Berdasarkan Role
11.1 Karyawan
- Asset Saya → menu Peminjaman → halaman Peminjaman Asset dengan 4 tab: Permintaan Peminjaman (chip Pengajuan, Dikonfirmasi, Ditolak, Dibatalkan), Sedang Dipinjam (chip Semua, Jatuh Tempo, Terlambat), Pengembalian (chip Pengajuan, Selesai) dan Riwayat Peminjaman (tanpa chip).
- Tekan + → Tambah Peminjaman Asset → Ajukan Peminjaman.
- Selama masih Menunggu Konfirmasi: detail menyediakan Batalkan. Setelah admin mengonfirmasi: banner di Asset Saya / detail → Terima Asset atau Batalkan.
- Selama aktif: kartu peminjaman aktif di Asset Saya, pengingat jatuh tempo, label Terlambat bila lewat.
- Kembalikan Asset di detail peminjaman aktif → pantau di tab Pengembalian; setelah selesai peminjaman pindah ke tab Riwayat.
11.2 Admin Asset
- Asset Karyawan → aksi Peminjaman (juga panel ringkasan "perlu diproses" dan "jatuh tempo").
- Tab Permintaan Peminjaman → chip Pengajuan → detail → Setujui/Tolak per asset → Konfirmasi → isi jadwal → Submit.
- Tab Pengembalian → chip Pengajuan → detail → Normal/Rusak per asset → Konfirmasi.
- Tab Sedang Dipinjam (chip Jatuh Tempo / Terlambat) memantau peminjaman aktif; tab Riwayat Peminjaman (read-only) menelusuri peminjaman yang sudah selesai.
11.3 Approver
Dijalankan Admin Asset (§11.2 langkah 2).
11.4 System
Lock company, penomoran, penguncian/pelepasan asset, validasi jadwal dalam periode, perhitungan sisa_hari/terlambat/dapat_dikembalikan, asset_trx_log, penutupan peminjaman asal, notifikasi setelah commit.
12Flow Diagram
flowchart TD
A[Karyawan ajukan peminjaman] --> B{Validasi}
B -->|Gagal| X[Pesan error]
B -->|OK| C[status pengajuan, asset proses, notif admin]
C --> D{Admin putuskan per asset}
C -->|Karyawan Batalkan| H0[status dibatalkan, semua asset tersedia, notif admin]
D -->|Semua ditolak| E[status ditolak, asset tersedia, notif karyawan]
D -->|Ada yang disetujui + jadwal| F[status dikonfirmasi, asset ditolak tersedia, notif karyawan]
F --> G{Karyawan}
G -->|Batalkan| H[status dibatalkan, asset disetujui tersedia, notif admin]
G -->|Terima Asset| I[status aktif, asset dipinjam, asset_trx_log, notif admin]
I --> J{Tanggal kembali}
J -->|H-1 atau lewat| K[Pengingat jatuh tempo / Terlambat]
J --> L[Karyawan: Kembalikan Asset]
K --> L
L --> M[Header pengembalian status pengajuan, notif admin]
M --> N[Admin isi kondisi dan pilih lokasi gudang tiap asset]
N --> O[Asset tersedia atau rusak di gudang, asset_trx_log]
O --> P[Pengembalian selesai, peminjaman asal selesai, notif karyawan]
P --> Q[Tab Riwayat: peminjaman selesai + pengembalian]
13Sequence Diagram
13.1 Pengajuan sampai aktif
sequenceDiagram
actor K as Karyawan
actor A as Admin
participant M as Mobile
participant CK as AssetKaryawanPeminjaman_c
participant CA as AssetAdminPeminjaman_c
participant P as AssetPeminjamanKaryawan_m
participant DB
participant N as AssetNotification
K->>M: Pilih asset, keperluan, tujuan, tanggal, Ajukan
M->>CK: POST /asset/karyawan/peminjaman
CK->>P: store()
P->>DB: lock company, cek karyawan, asset tersedia, tidak aktif
P->>DB: INSERT header + item, UPDATE asset_data proses
CK->>N: peminjaman.diajukan (admin)
A->>M: Setujui/Tolak per asset, isi jadwal, Submit
M->>CA: POST /asset/peminjaman-karyawan/{id}/konfirmasi
CA->>P: konfirmasiAdmin()
P->>DB: UPDATE item keputusan, UPDATE header dikonfirmasi + jadwal
P->>DB: UPDATE asset_data ditolak menjadi tersedia
CA->>N: peminjaman.disetujui (karyawan)
K->>M: Terima Asset
M->>CK: POST /asset/karyawan/peminjaman/{id}/terima
CK->>P: terimaKaryawan()
P->>DB: UPDATE asset_data dipinjam @karyawan, header aktif
P->>DB: INSERT asset_trx_log peminjaman_karyawan
CK->>N: peminjaman.diterima (admin)
13.2 Pengembalian
sequenceDiagram
actor K as Karyawan
actor A as Admin
participant M as Mobile
participant CK as AssetKaryawanPeminjaman_c
participant CA as AssetAdminPengembalian_c
participant P as AssetPeminjamanKaryawan_m
participant DB
participant N as AssetNotification
K->>M: Kembalikan Asset
M->>CK: POST /asset/karyawan/peminjaman/{id}/pengembalian
CK->>P: ajukanPengembalianKaryawan()
P->>DB: cek aktif, asset masih dipinjam, tidak ada pengembalian/offboarding
P->>DB: INSERT header pengembalian (id_peminjaman_asal) + item
CK->>N: pengembalian.diajukan (admin)
A->>M: Isi Normal/Rusak per asset, Konfirmasi
M->>CA: POST /asset/pengembalian-karyawan/{id}/konfirmasi
CA->>P: konfirmasiPengembalianAdmin()
P->>DB: UPDATE item, UPDATE asset_data tersedia atau rusak @gudang, id_asset_lokasi_m
P->>DB: INSERT asset_trx_log pengembalian_karyawan
P->>DB: UPDATE pengembalian selesai, peminjaman asal selesai
CA->>N: pengembalian.dikonfirmasi (karyawan)
13.3 Pembatalan oleh karyawan (v1.6)
sequenceDiagram
actor K as Karyawan
participant M as Mobile
participant CK as AssetKaryawanPeminjaman_c
participant P as AssetPeminjamanKaryawan_m
participant DB
participant N as AssetNotification
K->>M: Batalkan (status pengajuan atau dikonfirmasi)
M->>CK: POST /asset/karyawan/peminjaman/{id}/batalkan
CK->>P: batalkanKaryawan()
P->>DB: lock company, header milik karyawan FOR UPDATE
P->>DB: cek status pengajuan atau dikonfirmasi, karyawan aktif
P->>DB: UPDATE asset_data proses + gudang menjadi tersedia (item pengajuan: semua, dikonfirmasi: disetujui)
P->>DB: UPDATE header dibatalkan, ket_tolak
CK->>N: peminjaman.dibatalkan (admin)
14Status Transaksi
Kolom status (enum status_peminjaman: pengajuan, dikonfirmasi, ditolak, aktif, selesai, dibatalkan (v1.6, via SQL manual); default pengajuan). Di API dikirim sebagai status_approval. Enum ini dipakai bersama baris pengembalian, tetapi kode hanya memakai dibatalkan untuk peminjaman.
14.1 Peminjaman (jenis_status = peminjaman)
| Status | Label API | Tab | Arti | Aksi berikutnya |
|---|---|---|---|---|
pengajuan |
Menunggu Konfirmasi | pengajuan | Menunggu keputusan admin | Admin: Konfirmasi; Karyawan: Batalkan |
dikonfirmasi |
Dikonfirmasi | dikonfirmasi | Minimal satu asset disetujui, jadwal ditetapkan | Karyawan: Terima / Batalkan |
ditolak |
Ditolak | ditolak | Semua asset ditolak admin. Data sebelum v1.6 juga memuat pembatalan karyawan (ket_tolak = "Dibatalkan oleh karyawan.") |
– |
dibatalkan |
Dibatalkan | dibatalkan | Dibatalkan karyawan saat pengajuan atau dikonfirmasi (v1.6) |
– |
aktif |
Aktif (mobile: Terlambat bila lewat tanggal kembali) | aktif | Asset dipegang karyawan | Karyawan: Kembalikan Asset |
selesai |
Selesai | selesai (list: tab riwayat) |
Seluruh asset sudah kembali | – (tidak tampil di tab Permintaan/Sedang Dipinjam; tampil di tab Riwayat) |
14.2 Pengembalian (jenis_status = pengembalian)
| Status | Label API | Tab | Arti |
|---|---|---|---|
pengajuan |
Menunggu Konfirmasi | pengajuan | Menunggu pemeriksaan admin |
dikonfirmasi |
Dikonfirmasi | pengajuan | Hanya dipakai pengembalian yang dibuat offboarding |
ditolak |
Ditolak | selesai | ⚠️ Tidak pernah di-set |
selesai |
Selesai | selesai | Kondisi dicatat, asset kembali ke gudang |
14.3 Status item (status_item, enum approval_item)
| Konteks | Nilai DB | Nilai API | Arti |
|---|---|---|---|
| Peminjaman | NULL | '' |
Belum diputuskan |
| Peminjaman | disetujui / ditolak |
sama | Keputusan admin (+ket_tolak) |
| Pengembalian | disetujui |
normal |
Kondisi normal |
| Pengembalian | ditolak |
rusak |
Kondisi rusak (ket_tolak = keterangan kerusakan) |
Migrasi 2026_09_17_000009: nilai lama 1=pengajuan, 2=dikonfirmasi, 3=ditolak, 4=aktif (peminjaman) / selesai (pengembalian).
15State Transition
stateDiagram-v2
[*] --> pengajuan: Ajukan Peminjaman (Karyawan)
pengajuan --> dikonfirmasi: Konfirmasi, ada asset disetujui (Admin)
pengajuan --> ditolak: Konfirmasi, semua ditolak (Admin)
dikonfirmasi --> aktif: Terima Asset (Karyawan)
pengajuan --> dibatalkan: Batalkan (Karyawan)
dikonfirmasi --> dibatalkan: Batalkan (Karyawan)
aktif --> selesai: Pengembalian dikonfirmasi (Admin)
ditolak --> [*]
dibatalkan --> [*]
selesai --> [*]
| Status Awal | Action | Status Akhir | Actor |
|---|---|---|---|
| – | Ajukan Peminjaman | pengajuan | Karyawan |
| pengajuan | Konfirmasi (≥1 disetujui) | dikonfirmasi | Admin |
| pengajuan | Konfirmasi (semua ditolak) | ditolak | Admin |
| dikonfirmasi | Terima Asset | aktif | Karyawan |
| pengajuan | Batalkan | dibatalkan | Karyawan |
| dikonfirmasi | Batalkan | dibatalkan | Karyawan |
| aktif | Ajukan pengembalian | aktif (+ header pengembalian pengajuan) |
Karyawan |
| aktif | Pengembalian dikonfirmasi | selesai | Admin (otomatis) |
| pengajuan (pengembalian) | Konfirmasi kondisi | selesai | Admin |
Status asset_data:
| Dari | Ke | Trigger |
|---|---|---|
| tersedia @gudang | proses | Ajukan peminjaman |
| proses | tersedia @gudang | Asset ditolak admin / peminjaman dibatalkan karyawan (pengajuan: semua item; dikonfirmasi: item disetujui) |
| proses @gudang | dipinjam @karyawan | Terima Asset |
| dipinjam @karyawan | tersedia @gudang | Pengembalian, kondisi normal |
| dipinjam @karyawan | rusak @gudang | Pengembalian, kondisi rusak |
16Struktur Database
| Table | Type | Fungsi |
|---|---|---|
asset_trx_peminjaman_karyawan |
Transaction Header | Peminjaman & pengembalian (jenis_status) |
asset_trx_peminjaman_karyawan_item |
Transaction Detail | Asset per transaksi + keputusan/kondisi |
asset_data |
Master | Status & pemegang asset |
asset_kategori_m |
Master | Nama kategori (snapshot) |
data_karyawan, master_cabang, master_departemen, master_jabatan |
Master HR | Karyawan aktif, cabang, departemen, jabatan |
asset_admin |
Master akses | Admin asset |
master_company |
Master | Lock transaksi |
asset_trx_log |
Log | peminjaman_karyawan, pengembalian_karyawan |
asset_trx_offboarding_relasi |
Relasi | Menandai pengembalian yang dibuat offboarding |
notif_data, users |
Notifikasi | Inbox & token FCM |
17Relasi Database
erDiagram
ASSET_TRX_PEMINJAMAN_KARYAWAN ||--o{ ASSET_TRX_PEMINJAMAN_KARYAWAN_ITEM : "id_asset_trx_peminjaman"
ASSET_TRX_PEMINJAMAN_KARYAWAN |o--o{ ASSET_TRX_PEMINJAMAN_KARYAWAN : "id_peminjaman_asal (pengembalian)"
ASSET_DATA ||--o{ ASSET_TRX_PEMINJAMAN_KARYAWAN_ITEM : "id_asset_data"
DATA_KARYAWAN ||--o{ ASSET_TRX_PEMINJAMAN_KARYAWAN : "id_karyawan (logis)"
ASSET_DATA ||--o{ ASSET_TRX_LOG : "id_asset_data"
ASSET_TRX_PEMINJAMAN_KARYAWAN ||--o{ ASSET_TRX_LOG : "id_transaksi (logis)"
ASSET_TRX_PEMINJAMAN_KARYAWAN ||--o| ASSET_TRX_OFFBOARDING_RELASI : "id_tabel (logis)"
ASSET_TRX_PEMINJAMAN_KARYAWAN {
varchar id_asset_trx_peminjaman_karyawan PK
varchar no_trx
date tgl_peminjaman
date tgl_dikembalikan
varchar keperluan
varchar id_karyawan
enum jenis_status
varchar id_peminjaman_asal FK
enum status
varchar id_company
}
ASSET_TRX_PEMINJAMAN_KARYAWAN_ITEM {
varchar id_asset_trx_peminjaman_item PK
varchar id_asset_trx_peminjaman FK
varchar id_asset_data FK
enum status_item
varchar ket_tolak
}
ASSET_DATA {
varchar id_asset_data PK
enum status_asset
enum pemegang_asset
varchar id_pemegang_asset
}
ASSET_TRX_LOG {
varchar id_asset_trx_log PK
varchar id_asset_data FK
enum jenis_log
varchar id_transaksi
}
Semua JOIN mencocokkan id_company. id_peminjaman_asal memiliki ref di DBML (self-reference).
18Data Dictionary
18.1 asset_trx_peminjaman_karyawan
| Field | Type | Null | Default | Key | Description |
|---|---|---|---|---|---|
| id_asset_trx_peminjaman_karyawan | varchar(50) | No | – | PK | id_company + YYYYMM + 3 digit |
| no_trx | varchar(50) | No | '' | – | KRY05-YYYYMMNNN (tidak unique) |
| tgl_peminjaman | date | No | CURRENT_DATE | – | Tanggal pinjam (API: tgl_pinjam) |
| tgl_dikembalikan | date | No | CURRENT_DATE | – | Tanggal wajib kembali (API: tgl_kembali) |
| keperluan | varchar(255) | No | '' | – | kantor/dinas/pelatihan/lainnya |
| tujuan_peminjaman | varchar(255) | No | '' | – | Teks tujuan |
| tgl_serah_terima | date | No | 0000-00-00 | – | Jadwal serah terima (admin) |
| jam_serah_terima | time | No | 00:00:00 | – | Jam serah terima |
| keterangan_serah_terima | varchar(255) | – | '' | – | Catatan admin |
| ket_tolak | varchar(255) | No | '' | – | "Seluruh asset peminjaman ditolak." (status ditolak) / "Dibatalkan oleh karyawan." (status dibatalkan; data lama: ditolak) |
| id_karyawan, nik_karyawan, nama_karyawan | varchar | – | '' | IDX | Peminjam (snapshot) |
| jenis_status | enum(peminjaman, pengembalian) | No | peminjaman | IDX | Jenis baris |
| id_peminjaman_asal | varchar(50) | Yes | NULL | FK | Wajib untuk pengembalian |
| status | enum(pengajuan, dikonfirmasi, ditolak, aktif, selesai, dibatalkan) | No | pengajuan | IDX | API: status_approval. dibatalkan ditambah v1.6 (SQL manual) |
| user_approval / tgl_approval | varchar / timestamp | – | '' / NULL | – | Admin pemutus / pemeriksa pengembalian |
| id_company | varchar(50) | No | '' | IDX | Company |
| tgl_input, user_input | – | – | – | IDX | Pembuat (API: tgl_pengajuan) |
| tgl_update, user_update | – | Yes | NULL | – | Perubahan terakhir; dipakai sebagai tanggal diterima saat aktif, sebagai waktu pembatalan saat dibatalkan, dan sebagai urutan tab Riwayat saat selesai |
18.2 asset_trx_peminjaman_karyawan_item
| Field | Type | Null | Default | Key | Description |
|---|---|---|---|---|---|
| id_asset_trx_peminjaman_item | varchar(50) | No | – | PK | id_company + YYYYMM + 3 digit |
| id_asset_trx_peminjaman | varchar(50) | – | – | FK, IDX | → header |
| id_asset_data | varchar(50) | – | – | FK, IDX | → asset_data |
| nama_asset, deskripsi_asset, kategori_asset | varchar(255) | – | '' | – | Snapshot |
| status_item | enum(disetujui, ditolak) | Yes | NULL | IDX | Keputusan (peminjaman) / kondisi (pengembalian) |
| ket_tolak | varchar(255) | No | '' | – | Alasan tolak / keterangan kerusakan |
| id_asset_lokasi_m_tujuan | varchar(50) | No | '' | – | Lokasi gudang tujuan asset (pengembalian), dipilih admin (v1.1; migration 2026_10_05_000003, SQL sql-lokasi-retur-pengembalian.sql) |
| id_company, tgl_input, user_input | – | – | – | IDX | – |
18.3 asset_data (field yang dipakai)
status_asset (tersedia → proses → dipinjam → tersedia/rusak), pemegang_asset (gudang ↔ karyawan), id_pemegang_asset, id_asset_lokasi_m (lokasi gudang; dikosongkan saat dipinjam, diisi saat kembali), ket_rusak (diisi saat kembali rusak), kondisi_awal (disalin ke log), nama_asset, kode_asset, merek, deskripsi, tgl_update, user_update.
18.4 asset_trx_log
| Event | jenis_log | kondisi_awal → kondisi_akhir | posisi_awal → akhir | pemegang |
|---|---|---|---|---|
| Terima Asset | peminjaman_karyawan |
kondisi_awal → kondisi_awal | gudang → karyawan | karyawan (id, nama) |
| Pengembalian | pengembalian_karyawan |
kondisi_awal → normal/rusak | karyawan → gudang | gudang (kosong) |
19Database Index
| Index | Table | Columns | Tujuan |
|---|---|---|---|
atpk_admin_list_idx |
header | (id_company, jenis_status, status, tgl_input, id_asset_trx_peminjaman_karyawan) | List admin per jenis & tab |
atpk_employee_list_idx |
header | (id_company, id_karyawan, jenis_status, status, tgl_input, id_asset_trx_peminjaman_karyawan) | List karyawan |
atpki_loan_status_idx |
item | (id_asset_trx_peminjaman, id_company, status_item, id_asset_data) | Hitung item, cek asset per header |
Dibuat migration 2026_09_17_000006, dibuat ulang 2026_09_17_000009. Index tidak berubah di v1.6 (SQL dibatalkan tidak menyentuh index). Filter kondisi (tgl kembali) dan urutan tab Riwayat (tgl_update) tidak memiliki index khusus (⚠️ belum diukur). Pencarian hasActivePeminjamanAsset/hasActivePengembalianAsset berdasarkan atpki.id_asset_data tidak diawali kolom index (⚠️ belum diukur).
20Backend Architecture
RouteAsset.php (prefix /asset, jwt.api + jwt.ctx)
↓
AssetAdminPeminjaman_c + AssetAdminPengembalian_c (admin) / AssetKaryawanPeminjaman_c (karyawan; terima & batalkan lewat prosesSerahTerimaPeminjaman)
↓ validateRequest + accessError
AssetPeminjamanKaryawan_m ← Asset_m (lock, sequence, getLockedAssets, findActiveKaryawan, findAssetAdmin, hasActiveOnboardingAsset)
↓ ← AssetOffboardingKaryawan_m::hasActiveOffboardingAsset
DB::transaction (retry 3)
↓
ApiResponse + AssetNotification (setelah commit)
| Layer | File/Class | Function |
|---|---|---|
| Route | RouteAsset.php |
15 route peminjaman/pengembalian |
| Controller | AssetAdminPeminjaman_c (peminjaman), AssetAdminPengembalian_c (pengembalian) |
getPeminjamanKaryawan, getDetailPeminjamanKaryawan, konfirmasiPeminjamanKaryawan, getPengembalianKaryawan, getDetailPengembalianKaryawan, konfirmasiPengembalianKaryawan |
| Controller | AssetKaryawanPeminjaman_c |
getPeminjamanOptions, getPeminjamanSaya, storePeminjamanSaya, getDetailPeminjamanSaya, terimaPeminjamanSaya, batalkanPeminjamanSaya, ajukanPengembalianSaya, getPengembalianSaya, getDetailPengembalianSaya |
| Model | AssetPeminjamanKaryawan_m |
getOptions, getPeminjaman/Page, getPengembalian(Admin), getDetail(Admin), getDetailPengembalian(Admin), store, konfirmasiAdmin, terimaKaryawan, batalkanKaryawan, ajukanPengembalianKaryawan, konfirmasiPengembalianAdmin (lokasi gudang per item via resolveItemLocations di Asset_m), getPendingForEmployee, getPendingForAdmin, countPendingForAdmin, getJatuhTempo, countJatuhTempo; v1.6: applyKondisiAktifFilter, attachRiwayatPengembalian, pengembalianForDetail, peminjamanFilters |
| Enum | PeminjamanStatus |
pengajuan/dikonfirmasi/ditolak/aktif/selesai/dibatalkan (dipakai lewat konstanta model) |
| Support | AssetNotification |
Event peminjaman.*, pengembalian.* |
21Backend Business Logic
21.1 Ajukan — store
Lock → karyawan aktif → getLockedAssets (tersedia + gudang) → tidak aktif di onboarding/peminjaman → tgl_kembali ≥ tgl_pinjam → ID + KRY05-… → INSERT header (pengajuan, jadwal kosong) → INSERT item (status_item NULL) → UPDATE asset proses → notif peminjaman.diajukan. Response 201.
21.2 Konfirmasi admin — konfirmasiAdmin
Lock → admin → header peminjaman FOR UPDATE → bukan milik offboarding → status pengajuan → semua item diputuskan (tolak wajib alasan) → UPDATE item → bila ada disetujui: jadwal wajib, format valid, di dalam periode pinjam → UPDATE header (dikonfirmasi/ditolak, jadwal atau nol, ket_tolak, user_approval, tgl_approval) → asset ditolak → tersedia @gudang → notif peminjaman.disetujui/ditolak. Response 200 (+jumlah_disetujui, jumlah_ditolak).
21.3 Terima — terimaKaryawan
Lock → header milik karyawan FOR UPDATE → status dikonfirmasi → karyawan aktif → item disetujui → asset harus proses @gudang → UPDATE asset dipinjam @karyawan (id_asset_lokasi_m = NULL) → header aktif → INSERT asset_trx_log peminjaman_karyawan → notif peminjaman.diterima.
21.4 Batalkan — batalkanKaryawan (v1.6)
Lock company → header milik karyawan FOR UPDATE (lockedEmployeeHeader, hanya jenis_status = peminjaman) → status harus pengajuan atau dikonfirmasi → karyawan aktif → ambil asset item: dari pengajuan semua item dengan status_item NULL, dari dikonfirmasi item disetujui (FOR UPDATE) → UPDATE asset_data menjadi tersedia @gudang (id_pemegang_asset = '') hanya yang masih proses + gudang → header dibatalkan, ket_tolak = "Dibatalkan oleh karyawan.", tgl_update, user_update → notif peminjaman.dibatalkan. Tidak ada asset_trx_log. Controller: batalkanPeminjamanSaya → prosesSerahTerimaPeminjaman($diterima = false). Response 200.
21.5 Ajukan pengembalian — ajukanPengembalianKaryawan
Lock → peminjaman milik karyawan aktif → item disetujui → semua asset masih dipinjam oleh karyawan → tidak ada pengembalian pengajuan/dikonfirmasi → tidak di offboarding → INSERT header jenis_status = pengembalian, id_peminjaman_asal, salinan periode/keperluan, pengajuan → INSERT item (salinan item disetujui, status_item NULL) → notif pengembalian.diajukan. Response 201.
21.6 Konfirmasi pengembalian — konfirmasiPengembalianAdmin
Lock → admin → header pengembalian pengajuan → peminjaman asal ada & aktif → semua item diisi kondisi + lokasi gudang (resolveItemLocations) → asset masih dipinjam oleh karyawan → per item: UPDATE item (disetujui = normal / ditolak = rusak + keterangan, id_asset_lokasi_m_tujuan), UPDATE asset (tersedia/rusak @gudang, id_asset_lokasi_m pilihan admin, ket_rusak), siapkan asset_trx_log pengembalian_karyawan → INSERT log → header pengembalian selesai (+user_approval, tgl_approval) → bila tidak ada asset pinjaman yang masih dipegang: peminjaman asal selesai → notif pengembalian.dikonfirmasi. Response 200 (+jumlah_normal, jumlah_rusak).
21.7 Query baca
- Options: asset
tersedia@gudang yang tidak ada di peminjaman berjalan (item tidak ditolak) + daftar keperluan. - List peminjaman: tab Permintaan/Sedang Dipinjam tidak menampilkan
selesai, danaktifhanya jika asset masih dipegang karyawan. Filter tab:pengajuan,dikonfirmasi,ditolak,aktif,dibatalkan,riwayat(v1.6). Tab tanpa nilai (hanyalimit/offset) tidak memfilter status, kecualiselesaitetap disembunyikan. - Kondisi (v1.6):
applyKondisiAktifFilterhanya dipanggil bilatab=aktif;terlambat=tgl_dikembalikan< hari ini,jatuh_tempo= hari ini sampai besok; diterapkan di query SQL sebelumlimit+1/offset. - Riwayat (v1.6):
peminjamanQuery(includeSelesai)+ filterstatus = selesai,reorder()ketgl_updatedesc, id desc;attachRiwayatPengembalianmengambil pengembalianselesaiuntuk seluruh id di halaman dengan satu query (JOIN item,SUMnormal/rusak) dan hanya menempelkan yangid_karyawan-nya sama dengan peminjaman. Pengembalian offboarding tidak dikecualikan. - Detail peminjaman (v1.6): key
pengembaliandaripengembalianForDetail(semua status, uruttgl_inputasc,id_karyawansama) yang memanggilgetDetail(..., includeOffboarding = true)per pengembalian. - List pengembalian: mengecualikan pengembalian milik offboarding (tab/list/detail Pengembalian).
- Detail: menghitung
terlambat,tgl_diterima(=tgl_updatesaat aktif),dapat_dikembalikan(hanya untuk karyawan). - Jatuh tempo: aktif,
tgl_dikembalikan ≤ besok, asset masih dipegang, belum ada pengembalian berjalan;sisa_harinegatif = terlambat.
22Dampak terhadap asset_data
| Field | Sebelum | Sesudah | Trigger |
|---|---|---|---|
| status_asset | tersedia | proses | Ajukan peminjaman |
| status_asset / pemegang | proses | tersedia / gudang | Asset ditolak admin; peminjaman dibatalkan (pengajuan: semua item; dikonfirmasi: item disetujui; hanya asset proses + gudang) |
| status_asset | proses | dipinjam | Terima Asset |
| pemegang_asset / id_pemegang_asset | gudang / '' | karyawan / id_karyawan | Terima Asset |
| id_asset_lokasi_m | lokasi gudang | NULL (v1.1) | Terima Asset |
| status_asset | dipinjam | tersedia | Pengembalian normal |
| status_asset | dipinjam | rusak | Pengembalian rusak |
| pemegang_asset / id_pemegang_asset | karyawan / id | gudang / '' | Pengembalian |
| id_asset_lokasi_m | NULL | lokasi gudang pilihan admin (v1.1) | Pengembalian |
| ket_rusak | – | keterangan (rusak) / '' (normal) | Pengembalian |
| tgl_update / user_update | – | now / pelaku | Setiap perubahan |
23API Documentation
| ID | Method | Endpoint | Function | Auth |
|---|---|---|---|---|
| API-001 | GET | /asset/peminjaman-karyawan |
List peminjaman (admin); tab termasuk dibatalkan/riwayat, kondisi |
Admin |
| API-002 | GET | /asset/peminjaman-karyawan/{id} |
Detail peminjaman (admin) + pengembalian[] |
Admin |
| API-003 | POST | /asset/peminjaman-karyawan/{id}/konfirmasi |
Setujui/tolak per asset + jadwal | Admin |
| API-004 | GET | /asset/pengembalian-karyawan |
List pengembalian (admin) | Admin |
| API-005 | GET | /asset/pengembalian-karyawan/{id} |
Detail pengembalian (admin) | Admin |
| API-006 | POST | /asset/pengembalian-karyawan/{id}/konfirmasi |
Konfirmasi kondisi + lokasi gudang per asset | Admin |
| API-007 | GET | /asset/karyawan/peminjaman/options |
Opsi keperluan & asset | JWT |
| API-008 | GET | /asset/karyawan/peminjaman |
List peminjaman saya; tab termasuk dibatalkan/riwayat, kondisi |
JWT |
| API-009 | POST | /asset/karyawan/peminjaman |
Ajukan peminjaman | JWT |
| API-010 | GET | /asset/karyawan/peminjaman/{id} |
Detail peminjaman saya + pengembalian[] |
JWT, pemilik |
| API-011 | POST | /asset/karyawan/peminjaman/{id}/terima |
Terima asset | JWT, pemilik |
| API-012 | POST | /asset/karyawan/peminjaman/{id}/batalkan |
Batalkan (status pengajuan atau dikonfirmasi) | JWT, pemilik |
| API-013 | POST | /asset/karyawan/peminjaman/{id}/pengembalian |
Ajukan pengembalian | JWT, pemilik |
| API-014 | GET | /asset/karyawan/pengembalian |
List pengembalian saya | JWT |
| API-015 | GET | /asset/karyawan/pengembalian/{id} |
Detail pengembalian saya | JWT, pemilik |
Terkait: GET /asset/karyawan/onboarding (Asset Saya: peminjaman[], jatuh_tempo[], peminjaman_aktif[]) dan GET /asset/summary (peminjaman_perlu_proses, peminjaman_pending, jatuh_tempo_jumlah, jatuh_tempo).
Tolak pengembalian dan perpanjangan: ⚠️ Belum ditemukan. (Batalkan pengajuan tersedia sejak v1.6.)
24Detail Setiap API
Header umum: Authorization: Bearer <token>, Content-Type: application/json, Accept, locale, versi-app. Format response { code, message, data }.
API-001 / API-008 — List Peminjaman
GET /asset/peminjaman-karyawan?tab=pengajuan&limit=20&offset=0 (admin)
GET /asset/karyawan/peminjaman?tab=aktif&kondisi=terlambat&limit=20&offset=0 (karyawan)
GET /asset/karyawan/peminjaman?tab=riwayat&limit=20&offset=0 (karyawan)
| Parameter | Nilai | Keterangan |
|---|---|---|
tab |
pengajuan | dikonfirmasi | ditolak | aktif | dibatalkan | riwayat |
Selain itu 422 "Filter peminjaman asset tidak valid." dibatalkan dan riwayat baru di v1.6 |
kondisi |
semua | jatuh_tempo | terlambat |
Opsional, baru v1.6. Hanya efektif bila tab=aktif (diabaikan di tab lain). terlambat: tgl_kembali < hari ini; jatuh_tempo: tgl_kembali hari ini sampai besok (terlambat tidak masuk); semua/kosong: semua aktif. Difilter di SQL sebelum paginasi. Nilai lain 422 |
limit |
1–50 (default 20) | "Limit maksimal adalah 50 data." |
offset |
≥ 0 (default 0) | – |
Tanpa tab, limit, dan offset respons berupa array (non-paginasi) dan kondisi diabaikan. Kegagalan validasi: HTTP 422, message "Parameter daftar peminjaman asset tidak valid.", errors.kondisi = ["Filter kondisi peminjaman asset tidak valid."] (EN: "Invalid asset loan condition filter."), errors.tab = ["Filter peminjaman asset tidak valid."].
{
"code": "SUCCESS",
"message": "Data peminjaman asset berhasil ditemukan.",
"data": {
"filters": [
{"value": "pengajuan", "label": "Pengajuan"},
{"value": "dikonfirmasi", "label": "Dikonfirmasi"},
{"value": "ditolak", "label": "Ditolak"},
{"value": "aktif", "label": "Aktif"},
{"value": "dibatalkan", "label": "Dibatalkan"}
],
"records": [
{
"id": "CMP001202609007",
"no_trx": "KRY05-202609007",
"tgl_pengajuan": "2026-09-26",
"keperluan": "dinas",
"tujuan_peminjaman": "Presentasi klien di Surabaya",
"tgl_pinjam": "2026-09-28",
"tgl_kembali": "2026-10-02",
"tgl_serah_terima": "2026-09-28",
"jam_serah_terima": "08:30:00",
"keterangan_serah_terima": "Ambil di ruang IT",
"ket_tolak": "",
"id_karyawan": "KRY0003",
"nik_karyawan": "EMP-003",
"nama_karyawan": "Andi Contoh",
"jenis_status": "peminjaman",
"id_peminjaman_asal": "",
"status_approval": "aktif",
"status_label": "Aktif",
"tab": "aktif",
"jumlah_asset": 1,
"tgl_diterima": "2026-09-28",
"terlambat": false
}
],
"pagination": {"limit": 20, "offset": 0, "next_offset": 1, "has_more": false}
}
}
filters sengaja tidak memuat riwayat (tab Riwayat punya tab utama sendiri di mobile).
Tab riwayat (v1.6). Hanya peminjaman selesai (company login; karyawan login untuk endpoint karyawan), urut tgl_update desc lalu id desc, paginasi limit+1 tanpa COUNT. Setiap record memuat field di atas plus pengembalian[] (urut tgl_approval asc; hanya pengembalian selesai milik peminjaman dan karyawan yang sama; satu query per halaman, tanpa N+1). Pengembalian yang berasal dari offboarding ikut ditampilkan (keputusan produk).
{
"id": "CMP001202610001",
"no_trx": "KRY05-202610001",
"status_approval": "selesai",
"status_label": "Selesai",
"tab": "selesai",
"jumlah_asset": 2,
"tgl_diterima": "",
"terlambat": false,
"pengembalian": [
{"id": "CMP001202610002", "no_trx": "KRY05-202610002", "tgl_selesai": "2026-10-28", "jumlah_normal": 0, "jumlah_rusak": 1}
]
}
(field header lain sama dengan contoh di atas). tgl_selesai = tanggal tgl_approval pengembalian ("" bila kosong); jumlah_normal = item disetujui, jumlah_rusak = item ditolak.
API-002 / API-010 — Detail Peminjaman
GET /asset/peminjaman-karyawan/{id} (admin)
GET /asset/karyawan/peminjaman/{id} (karyawan, hanya miliknya)
Field sama dengan record list (tanpa id_karyawan, nik/nama di dalam karyawan) ditambah:
{
"dapat_dikembalikan": true,
"karyawan": {"nik": "EMP-003", "nama_karyawan": "Andi Contoh", "cabang": "Kantor Pusat", "divisi": "Sales", "jabatan": "Staff Sales"},
"assets": [
{
"id_item": "CMP001202609021",
"id_asset_data": "CMP00120260100009",
"kode_asset": "PRJ-0002",
"nama_asset": "Proyektor Mini",
"deskripsi": "Kabel HDMI, tas",
"kategori_asset": "Proyektor",
"merek": "MerekY",
"status_item": "disetujui",
"ket_tolak": "",
"id_asset_lokasi_m_tujuan": "",
"nama_lokasi_tujuan": ""
}
],
"pengembalian": [
{
"id": "CMP001202610002",
"no_trx": "KRY05-202610002",
"status_approval": "selesai",
"status_label": "Selesai",
"tgl_pengajuan": "2026-10-28",
"tgl_selesai": "2026-10-28",
"assets": [
{
"id_item": "CMP001202610003",
"id_asset_data": "CMP00120260100009",
"nama_asset": "Proyektor Mini",
"kondisi": "rusak",
"keterangan_kerusakan": "Lensa tergores",
"lokasi_tujuan": "Gudang Servis"
}
]
}
]
}
dapat_dikembalikan hanya bernilai true pada endpoint karyawan. 404: "Peminjaman asset tidak ditemukan."
pengembalian[] (v1.6) hanya ada pada detail peminjaman (bukan detail pengembalian), boleh kosong, berisi semua status pengembalian milik peminjaman itu (urut tgl_input asc, id_karyawan sama dengan peminjaman). tgl_selesai berisi tanggal tgl_approval hanya bila status selesai, selain itu "". assets[].kondisi: normal (dari disetujui), rusak (dari ditolak), selain itu ""; keterangan_kerusakan = ket_tolak item; lokasi_tujuan = nama lokasi gudang atau "". Pengembalian dari offboarding ikut tampil. Backend memanggil getDetail per pengembalian (RISK-012).
API-003 — Konfirmasi Peminjaman (Admin)
POST /asset/peminjaman-karyawan/{id}/konfirmasi
{
"items": [
{"id_item": "CMP001202609021", "status_item": "disetujui"},
{"id_item": "CMP001202609022", "status_item": "ditolak", "ket_tolak": "Sedang dijadwalkan servis"}
],
"tgl_serah_terima": "2026-09-28",
"jam_serah_terima": "08:30",
"keterangan_serah_terima": "Ambil di ruang IT"
}
| Field | Rule | Message |
|---|---|---|
| items | required|array|min:1 | Keputusan asset wajib diisi. |
| items.*.id_item | required|string|max:50|distinct | Asset yang sama tidak boleh dikirim lebih dari sekali. |
| items.*.status_item | required|in:disetujui,ditolak | Keputusan setiap asset wajib dipilih. / Keputusan asset harus disetujui atau ditolak. |
| items.*.ket_tolak | nullable|max:255|required_if ditolak | Alasan penolakan asset wajib diisi. |
| tgl_serah_terima | nullable|date_format:Y-m-d (wajib di model bila ada disetujui) | Format tanggal serah terima harus YYYY-MM-DD. |
| jam_serah_terima | nullable|date_format:H:i (wajib di model bila ada disetujui) | Format jam serah terima harus HH:MM. |
| keterangan_serah_terima | nullable|max:255 | – |
Database impact:
UPDATE asset_trx_peminjaman_karyawan_item (status_item, ket_tolak)
UPDATE asset_trx_peminjaman_karyawan (status, jadwal, ket_tolak, user_approval, tgl_approval)
UPDATE asset_data (item ditolak → tersedia @gudang)
INSERT notif_data
{ "code": "UPDATED", "message": "Peminjaman asset berhasil dikonfirmasi.",
"data": { "id": "CMP001202609007", "no_trx": "KRY05-202609007", "status_approval": "dikonfirmasi",
"status_label": "Dikonfirmasi", "tab": "dikonfirmasi", "jumlah_disetujui": 1, "jumlah_ditolak": 1 } }
Semua ditolak: "Peminjaman asset berhasil ditolak.", status_approval: ditolak.
422: "Peminjaman asset tidak ditemukan.", "Pengembalian ini diproses melalui offboarding.", "Peminjaman asset sudah tidak menunggu konfirmasi.", "Seluruh asset peminjaman wajib dikonfirmasi.", "Tanggal/Jam serah terima wajib diisi.", "Format jadwal serah terima tidak valid.", "Tanggal serah terima harus berada dalam periode peminjaman." 500: "Terjadi kesalahan saat mengonfirmasi peminjaman asset."
API-004 / API-014 — List Pengembalian
GET /asset/pengembalian-karyawan?tab=pengajuan (admin)
GET /asset/karyawan/pengembalian?tab=selesai (karyawan)
tab: pengajuan (pengajuan/dikonfirmasi) | selesai (ditolak/selesai). Filters: Pengajuan, Selesai. Record sama dengan API-001 dengan jenis_status = pengembalian dan id_peminjaman_asal terisi.
API-005 / API-015 — Detail Pengembalian
GET /asset/pengembalian-karyawan/{id}
GET /asset/karyawan/pengembalian/{id}
Struktur seperti API-002; assets[].status_item bernilai normal/rusak (dipetakan dari disetujui/ditolak), ket_tolak = keterangan kerusakan. 404: "Pengembalian asset tidak ditemukan."
API-006 — Konfirmasi Pengembalian (Admin)
POST /asset/pengembalian-karyawan/{id}/konfirmasi
{
"items": [
{"id_item": "CMP001202610003", "kondisi": "rusak", "id_asset_lokasi_m": "CMP001202610001", "keterangan_kerusakan": "Lensa tergores"}
]
}
| Field | Rule | Message |
|---|---|---|
| items | required|array|min:1 | Kondisi asset wajib diisi. |
| items.*.id_item | required|distinct | Asset yang sama tidak boleh dikirim lebih dari sekali. |
| items.*.kondisi | required|in:normal,rusak | Kondisi setiap asset wajib dipilih. / Kondisi asset harus normal atau rusak. |
| items.*.keterangan_kerusakan | nullable|max:255|required_if rusak | Keterangan kerusakan asset wajib diisi. |
| items.*.id_asset_lokasi_m | required|string|max:50 | Lokasi gudang tujuan wajib dipilih. (v1.1) |
Database impact:
UPDATE asset_trx_peminjaman_karyawan_item (status_item, ket_tolak, id_asset_lokasi_m_tujuan)
UPDATE asset_data (tersedia / rusak @gudang, id_asset_lokasi_m, ket_rusak)
INSERT asset_trx_log (pengembalian_karyawan)
UPDATE asset_trx_peminjaman_karyawan (pengembalian → selesai; peminjaman asal → selesai)
INSERT notif_data
{ "code": "UPDATED", "message": "Pengembalian asset berhasil dikonfirmasi.",
"data": { "id": "CMP001202610001", "no_trx": "KRY05-202610001", "jenis_status": "pengembalian",
"id_peminjaman_asal": "CMP001202609007", "status_approval": "selesai", "status_label": "Selesai",
"tab": "selesai", "jumlah_normal": 0, "jumlah_rusak": 1 } }
422: "Pengembalian asset tidak ditemukan.", "Pengembalian asset sudah tidak menunggu konfirmasi.", "Peminjaman asal pengembalian tidak ditemukan.", "Peminjaman asal pengembalian tidak ditemukan atau sudah tidak aktif.", "Seluruh kondisi asset pengembalian wajib diisi.", "Satu atau beberapa asset tidak sedang dipinjam oleh karyawan.", "Lokasi gudang tujuan setiap asset wajib dipilih.", "Lokasi gudang tujuan tidak valid.", "Lokasi gudang tujuan asset tidak sesuai." 500: "Terjadi kesalahan saat mengonfirmasi pengembalian asset."
Detail pengembalian admin (API-005) mulai v1.1 memuat lokasi_gudang[] (gudang milik company), karyawan.id_cabang, dan assets[].id_asset_lokasi_m_tujuan serta nama_lokasi_tujuan (kosong sampai dikonfirmasi). Pesan umum validasi: "Data konfirmasi pengembalian belum lengkap."
API-007 — Opsi Peminjaman
GET /asset/karyawan/peminjaman/options
{
"code": "SUCCESS",
"message": "Opsi peminjaman asset berhasil ditemukan.",
"data": {
"keperluan": [
{"value": "kantor", "label": "Kantor"},
{"value": "dinas", "label": "Dinas"},
{"value": "pelatihan", "label": "Pelatihan"},
{"value": "lainnya", "label": "Lainnya"}
],
"assets": [
{"id_asset_data": "CMP00120260100009", "kode_asset": "PRJ-0002", "nama_asset": "Proyektor Mini",
"kategori_asset": "Proyektor", "merek": "MerekY", "deskripsi": "Kabel HDMI, tas"}
]
}
}
API-009 — Ajukan Peminjaman
POST /asset/karyawan/peminjaman
{
"keperluan": "dinas",
"tujuan_peminjaman": "Presentasi klien di Surabaya",
"tgl_pinjam": "2026-09-28",
"tgl_kembali": "2026-10-02",
"asset_ids": ["CMP00120260100009"]
}
| Field | Rule | Message |
|---|---|---|
| keperluan | required|string|max:255 | Keperluan wajib diisi. |
| tujuan_peminjaman | required|string|max:255 | Tujuan peminjaman wajib diisi. |
| tgl_pinjam | required|date_format:Y-m-d|after_or_equal:today | Tanggal pinjam wajib diisi. / Format … / Tanggal pinjam tidak boleh sebelum hari ini. |
| tgl_kembali | required|date_format:Y-m-d|after_or_equal:tgl_pinjam | Tanggal kembali wajib diisi. / Format … / Tanggal kembali tidak boleh sebelum tanggal pinjam. |
| asset_ids | required|array|min:1 | Asset yang dipinjam wajib dipilih. |
| asset_ids.* | required|max:50|distinct | Asset yang sama tidak boleh dipilih lebih dari sekali. |
Database impact: INSERT header (pengajuan), INSERT item, UPDATE asset_data (proses), INSERT notif_data.
{ "code": "CREATED", "message": "Pengajuan peminjaman asset berhasil dikirim.",
"data": { "id": "CMP001202609007", "no_trx": "KRY05-202609007", "status_approval": "pengajuan",
"status_label": "Menunggu Konfirmasi", "tab": "pengajuan" } }
422: "Karyawan tidak ditemukan atau tidak aktif.", "Satu atau beberapa asset tidak tersedia untuk dipinjam.", "…sedang diproses pada onboarding.", "…sedang diproses pada peminjaman lain.", "Nomor transaksi untuk periode ini sudah mencapai batas." 500: "Terjadi kesalahan saat menyimpan peminjaman asset."
API-011 — Terima Asset
POST /asset/karyawan/peminjaman/{id}/terima
Tanpa body. Database impact: UPDATE asset_data (dipinjam @karyawan), UPDATE header (aktif), INSERT asset_trx_log (peminjaman_karyawan), INSERT notif_data.
{ "code": "UPDATED", "message": "Asset peminjaman berhasil diterima.",
"data": { "id": "CMP001202609007", "no_trx": "KRY05-202609007", "status_approval": "aktif",
"status_label": "Aktif", "tab": "aktif", "jumlah_asset": 1 } }
422: "Peminjaman asset tidak ditemukan.", "Peminjaman asset belum dapat diterima.", "Karyawan tidak ditemukan atau tidak aktif.", "Tidak ada asset yang disetujui untuk diterima.", "Satu atau beberapa asset belum siap diserahterimakan." 500: "Terjadi kesalahan saat memproses serah terima asset."
API-012 — Batalkan Peminjaman (v1.6: pengajuan dan dikonfirmasi)
POST /asset/karyawan/peminjaman/{id}/batalkan
Tanpa body. Hanya pemilik transaksi (dari token) dan hanya baris jenis_status = peminjaman. Dari pengajuan seluruh asset item dilepas; dari dikonfirmasi hanya item disetujui. Hanya asset yang masih proses + gudang diubah menjadi tersedia @gudang. Database impact: UPDATE asset_data (tersedia @gudang), UPDATE header (dibatalkan, ket_tolak = "Dibatalkan oleh karyawan.", tgl_update, user_update), INSERT notif_data. Tidak ada asset_trx_log.
{ "code": "UPDATED", "message": "Peminjaman asset berhasil dibatalkan.",
"data": { "id": "CMP001202609007", "no_trx": "KRY05-202609007", "status_approval": "dibatalkan",
"status_label": "Dibatalkan", "tab": "dibatalkan" } }
EN: "Asset loan cancelled successfully." Notifikasi peminjaman.dibatalkan ke seluruh admin.
422 (BUSINESS_RULE_VIOLATION): "Peminjaman asset sudah tidak dapat dibatalkan." (status aktif, selesai, ditolak, atau dibatalkan), "Peminjaman asset tidak ditemukan." (milik karyawan lain, id tidak ada, atau id adalah baris pengembalian), "Karyawan tidak ditemukan atau tidak aktif." 500: "Terjadi kesalahan saat memproses serah terima asset."
API-013 — Ajukan Pengembalian
POST /asset/karyawan/peminjaman/{id}/pengembalian
Tanpa body (semua asset disetujui ikut dikembalikan). Database impact: INSERT header pengembalian, INSERT item, INSERT notif_data.
{ "code": "CREATED", "message": "Pengajuan pengembalian asset berhasil dikirim.",
"data": { "id": "CMP001202610001", "no_trx": "KRY05-202610001", "jenis_status": "pengembalian",
"id_peminjaman_asal": "CMP001202609007", "status_approval": "pengajuan",
"status_label": "Menunggu Konfirmasi", "tab": "pengajuan", "jumlah_asset": 1 } }
422: "Peminjaman asset tidak ditemukan.", "Hanya peminjaman aktif yang dapat dikembalikan.", "Tidak ada asset aktif yang dapat dikembalikan.", "Satu atau beberapa asset tidak sedang dipinjam oleh karyawan.", "Pengembalian asset sedang menunggu konfirmasi.", "Asset sedang diproses pada offboarding."
25Mobile Architecture
Page → Bloc (AssetPeminjamanBloc / AssetPeminjamanAdminBloc) → Api (AssetPeminjamanApi / AssetPeminjamanAdminApi) → ApiClient → Backend
| Layer | File | Function |
|---|---|---|
| Api | Repository/Service/assetPeminjamanApi.dart |
getPeminjaman, getOptions, store, getDetail, terima, batalkan, getPengembalian, getDetailPengembalian, ajukanPengembalian |
| Api | Repository/Service/assetPeminjamanAdminApi.dart |
getPeminjaman, getDetail, konfirmasi, getPengembalian, getDetailPengembalian, konfirmasiPengembalian (+ id_asset_lokasi_m per item) |
| Model | Repository/Model/Asset/mAssetPeminjaman.dart |
AssetPeminjamanAsset, AssetPeminjamanKeperluan, AssetPeminjamanOptions, AssetPeminjamanKaryawan, AssetPeminjamanApproval (kini termasuk dibatalkan) + parseAssetPeminjamanApproval, AssetPeminjamanRecord (+ pengembalian), AssetPeminjamanPengembalian & AssetPeminjamanPengembalianAsset (v1.6), ekstensi AssetPeminjamanRecordPengembalianX (isSelesai, jumlah normal/rusak, tanggal pengembalian terakhir), AssetPeminjamanPage, AssetPeminjamanRequest |
| Model | Repository/Model/Asset/mAssetPeminjamanAdmin.dart |
AssetPeminjamanAdminKeputusan, AssetPeminjamanAdminKonfirmasiRequest, AssetPengembalianAdminKondisi (kini + idLokasi), AssetPengembalianAdminKonfirmasiRequest; model bersama AssetWarehouseLocation (mAssetWarehouseLocation.dart) |
| Bloc | Bloc/Asset/AssetPeminjamanBloc.dart |
List peminjaman/pengembalian (LoadAssetPeminjamanList(tab, kondisi); state menyimpan activePeminjamanTab & activePeminjamanKondisi; kondisi di-reset ke semua bila tab diberikan tanpa kondisi), options, detail, submit, terima, batalkan, ajukan pengembalian |
| Bloc | Bloc/Asset/AssetPeminjamanAdminBloc.dart |
List, detail, konfirmasi peminjaman & pengembalian |
| UI karyawan | UI/Modul/Asset/AssetSaya/Peminjaman/* |
HalamanAssetPeminjaman, HalamanTambahAssetPeminjaman, HalamanDetailAssetPeminjaman, AssetPeminjamanPicker, AssetPeminjamanWidgets (berisi AssetPeminjamanPageMode, AssetPeminjamanKondisi, AssetPeminjamanTabBar, AssetPeminjamanRiwayatCard, AssetPeminjamanPengembalianSection) |
| UI admin | UI/Modul/Asset/AssetAdmin/AssetAdminKaryawan/Peminjaman/* |
HalamanAssetAdminPeminjaman, HalamanAssetAdminDetailPeminjaman, AssetPeminjamanAdminConfirmationSheet, AssetPeminjamanAdminWidgets |
| UI | UI/Modul/Asset/AssetSaya/HalamanAssetSaya.dart |
Banner terima, pengingat jatuh tempo, kartu peminjaman aktif |
| Route | Page | Event awal |
|---|---|---|
/asset_peminjaman |
Peminjaman Asset (argumen AssetPeminjamanPageMode; true lama → Pengembalian, selain itu Permintaan Peminjaman; lihat §26.1) |
Per mode: LoadAssetPeminjamanList() (peminjaman), (tab: 'aktif') (sedangDipinjam), (tab: 'riwayat') (riwayat), LoadAssetPengembalianList() (pengembalian) |
/asset_tambah_peminjaman |
Tambah Peminjaman Asset | LoadAssetPeminjamanOptions |
/asset_detail_peminjaman |
Detail Peminjaman Asset | LoadAssetPeminjamanDetail(id) |
/asset_detail_pengembalian |
Detail Pengembalian Asset (isPengembalian) |
LoadAssetPengembalianDetail(id) |
/asset_admin_peminjaman |
Peminjaman Asset (admin; argumen AssetPeminjamanPageMode, default peminjaman) |
LoadAssetPeminjamanAdminList / LoadAssetPengembalianAdminList sesuai mode |
/asset_admin_detail_peminjaman |
Detail Peminjaman (admin) | LoadAssetPeminjamanAdminDetail(id) |
/asset_admin_detail_pengembalian |
Detail Pengembalian (admin, isPengembalian) |
LoadAssetPengembalianAdminDetail(id) |
26Mobile Screen
26.1 Peminjaman Asset (Karyawan) — 4 tab (v1.6)
Tab utama (AssetPeminjamanTabBar, tanpa badge): Permintaan Peminjaman, Sedang Dipinjam, Pengembalian, Riwayat Peminjaman. Enum AssetPeminjamanPageMode kini {peminjaman, sedangDipinjam, pengembalian, riwayat} (sebelumnya hanya peminjaman/pengembalian). Lebar tiap tab = lebar layar / 4 dengan minimum 88 dp; bila tidak muat (mis. 320 px) bar dapat digulir horizontal dan tab terakhir tetap terjangkau.
| Tab | Chip | Sumber API |
|---|---|---|
| Permintaan Peminjaman | Pengajuan, Dikonfirmasi, Ditolak, Dibatalkan | GET …/peminjaman?tab=<chip>; chip dari filters API tanpa aktif dan riwayat (fallback enum lokal bila filters kosong) |
| Sedang Dipinjam | Semua, Jatuh Tempo, Terlambat | tab=aktif selalu dikirim; kondisi=jatuh_tempo/terlambat, Semua tidak mengirim kondisi |
| Pengembalian | Pengajuan, Selesai (tidak berubah) | GET …/pengembalian?tab= |
| Riwayat Peminjaman | – | tab=riwayat |
Daftar peminjaman di Bloc dipakai bersama tab Permintaan, Sedang Dipinjam, dan Riwayat; _listMatchesMode mencegah data tab lain bocor (mis. daftar aktif tidak tampil di tab Permintaan) dan menampilkan loading sampai daftar tab yang benar dimuat. Pemanggil lama dipetakan oleh _assetPeminjamanModeFrom (router): argumen enum dipakai apa adanya, true → Pengembalian, lainnya → Permintaan Peminjaman. NavigateToAssetPeminjaman menerima mode: atau pengembalian: true; menu Peminjaman di Asset Saya membuka Permintaan Peminjaman.
Kartu Permintaan/Sedang Dipinjam/Pengembalian: nomor, Tanggal Pengajuan / Tanggal Diterima (bila aktif), status (Menunggu Konfirmasi, Dikonfirmasi, Ditolak, Dibatalkan, Aktif, Terlambat), Keperluan, Tanggal Pinjam, Tanggal Kembali, "{jumlah} Asset", info jadwal serah terima, peringatan "Sudah lewat tanggal pengembalian. Silakan kembalikan asset sekarang." Tombol +.
Kartu Riwayat (AssetPeminjamanRiwayatCard): nomor transaksi, periode (Tanggal Pinjam / Tanggal Kembali), "{jumlah} Asset", badge Selesai, dan ringkasan pengembalian: satu pengembalian → "Dikembalikan {tgl} · N normal · M rusak"; lebih dari satu → dijumlahkan dengan tanggal terakhir dan jumlah transaksi ("Dikembalikan terakhir {tgl} · {n} pengembalian · …"). Bila peminjaman tidak punya pengembalian, ringkasan tidak ditampilkan; bila tanggalnya kosong tertulis "Dikembalikan -" (RISK-013). Kunci lokalisasi baru di assets/{id,en}/asset.json: menu.permintaan_peminjaman, menu.sedang_dipinjam, menu.riwayat_peminjaman, label.ringkasan_pengembalian(_banyak), pesan.belum_ada_riwayat_peminjaman, tab.dibatalkan, status.dibatalkan, serta tab.semua/jatuh_tempo/terlambat untuk chip kondisi.
26.2 Tambah Peminjaman Asset
| Field UI | Widget | Required | Source |
|---|---|---|---|
| Pilih Asset → Tambah Asset | Bottom sheet "Tambah Pinjam Asset" (cari nama/kode/merek) | Ya, min 1 | options.assets |
| Form Peminjaman: Pilih Keperluan | Bottom sheet | Ya | options.keperluan |
| Tujuan Peminjaman | Text field | Ya | Input |
| Tanggal Pinjam – Tanggal Kembali | Date range picker | Ya, kembali ≥ pinjam | Input |
| Tombol | Ajukan Peminjaman | – | – |
26.3 Detail Peminjaman / Pengembalian (Karyawan)
Header (Tanggal Pengajuan/Diterima, status, Nomor Pengajuan), Data Karyawan (Jabatan, Cabang, Departemen), Detail Peminjaman (Tanggal Pinjam, Tanggal Kembali, Keperluan, Tujuan Peminjaman), Jadwal Serah Terima, daftar asset dengan status Disetujui Admin/Ditolak Admin (+Alasan Penolakan) atau kondisi Normal/Rusak (+Keterangan Kerusakan) untuk pengembalian.
- Status pengajuan (v1.6): hanya tombol Batalkan (_EmployeeCancelAction, dialog "Peminjaman yang dibatalkan tidak dapat dipulihkan.").
- Status dikonfirmasi: panel "Siap untuk meminjam Asset?" + instruksi, tombol Batalkan dan Terima Asset (dialog konfirmasi).
- Status dibatalkan (v1.6): badge Dibatalkan (abu-abu), tanpa tombol aksi.
- Detail peminjaman memuat bagian Pengembalian (AssetPeminjamanPengembalianSection): satu kartu per transaksi pengembalian (nomor, status, tanggal, kondisi Normal/Rusak + keterangan kerusakan + lokasi tujuan per asset). Bagian disembunyikan bila pengembalian kosong atau pada detail pengembalian.
- dapat_dikembalikan: tombol Kembalikan Asset (dialog "Asset aktif pada peminjaman ini akan diajukan untuk dikembalikan.").
Alur status: AssetAlurStepper.peminjaman (Pengajuan → Konfirmasi → Dipinjam → Pengembalian → Selesai; transaksi pengembalian mulai dari langkah Pengembalian; disembunyikan bila ditolak) dan satu kartu timeline per aset (AssetPeminjamanTimelineCards). Transaksi peminjaman: Pengajuan (keperluan, periode) · Konfirmasi (jadwal serah terima; chip Ditolak per aset) · Dipinjam (tanggal diterima, batas kembali; chip Terlambat) · Pengembalian · Selesai (lokasi gudang). Transaksi pengembalian: Pengembalian (jadwal) · Konfirmasi (kondisi Normal/Rusak; chip Rusak) · Selesai (lokasi gudang). Alasan penolakan tetap di kartu aset. File: Shared/Widgets/AssetAlurWidgets.dart, Shared/Widgets/AssetPeminjamanTimeline.dart.
26.4 Peminjaman Asset (Admin)
Empat tab dan chip sama seperti karyawan (§26.1; kondisi dikirim ke GET /asset/peminjaman-karyawan). "Lihat semua" pada panel jatuh tempo di Asset Karyawan membuka tab Sedang Dipinjam (NavigateToAssetAdminPeminjaman(mode: sedangDipinjam)); aksi Peminjaman dan panel "perlu diproses" membuka Permintaan Peminjaman. Kartu menampilkan karyawan, keperluan, periode, jumlah asset; tab Riwayat memakai AssetPeminjamanRiwayatCard(showKaryawan: true) (+ nama/NIK karyawan).
26.5 Detail Peminjaman (Admin)
Data karyawan (Jabatan, Cabang, Departemen), detail peminjaman, daftar asset. Status pengajuan: tiap asset tombol Setujui/Tolak (+Alasan Penolakan) dan tombol bawah Konfirmasi → sheet Konfirmasi Peminjaman Asset (ringkasan, Jadwal Serah Terima dibatasi periode pinjam, Jam Serah Terima, Catatan Tambahan, Submit). Jadwal hanya diminta bila ada asset disetujui. Admin read-only pada peminjaman selesai (v1.6: kondisi editable kini memeriksa !isSelesai, sehingga form keputusan tidak muncul), label dan warna status Selesai benar di detail admin dan karyawan, serta bagian Pengembalian ikut tampil.
Stepper dan kartu timeline sama seperti §26.3; kartu timeline disembunyikan saat form keputusan admin terbuka.
26.6 Detail Pengembalian (Admin)
Status pengajuan: tiap asset Normal/Rusak (+Keterangan Kerusakan) dropdown Lokasi Tujuan Asset per asset (v1.1; default gudang di cabang karyawan) dan tombol Konfirmasi (langsung dikirim, tanpa sheet). Bila belum ada pilihan lokasi: "Pilih lokasi gudang tujuan untuk seluruh asset.". Setelah selesai: kondisi dan lokasi tujuan read-only.
Stepper dan kartu timeline transaksi pengembalian sama seperti §26.3; kartu timeline disembunyikan saat form kondisi dan lokasi admin terbuka.
26.7 Asset Saya
Banner per peminjaman dikonfirmasi (aksi terima), banner jatuh tempo (label dari sisa_hari), bagian Peminjaman Asset berisi kartu peminjaman aktif.
27Mobile → API
| UI Field | Mobile Variable | JSON Field | Type | Required |
|---|---|---|---|---|
| Pilih Keperluan | _selectedPurpose.value |
keperluan |
string | Ya |
| Tujuan Peminjaman | _detailController.text.trim() |
tujuan_peminjaman |
string | Ya |
| Tanggal Pinjam | tanggalPeminjamanApi(_borrowDate) |
tgl_pinjam |
Y-m-d | Ya |
| Tanggal Kembali | tanggalPeminjamanApi(_returnDate) |
tgl_kembali |
Y-m-d | Ya |
| Tambah Asset | _selectedAssets[].idAssetData |
asset_ids[] |
array | Ya |
| Setujui / Tolak (admin) | AssetPeminjamanAdminKeputusan.statusItem |
items[].status_item |
disetujui/ditolak |
Ya |
| Alasan Penolakan | ketTolak.trim() |
items[].ket_tolak |
string | Bila ditolak |
| Jadwal & Jam Serah Terima | sheet _handoverDate, _handoverTime |
tgl_serah_terima, jam_serah_terima |
Y-m-d, H:i | Bila ada disetujui |
| Catatan Tambahan | sheet | keterangan_serah_terima |
string | Tidak |
| Normal / Rusak (admin pengembalian) | AssetPengembalianAdminKondisi.kondisi |
items[].kondisi |
string | Ya |
| Lokasi Tujuan Asset (admin pengembalian) | AssetPengembalianAdminKondisi.idLokasi |
items[].id_asset_lokasi_m |
string | Ya (v1.1) |
| Keterangan Kerusakan | keteranganKerusakan.trim() |
items[].keterangan_kerusakan |
string | Bila rusak |
| Chip Permintaan Peminjaman | _permintaanTab / activePeminjamanTab |
query tab |
string | Ya |
| Chip Sedang Dipinjam | AssetPeminjamanKondisi.apiValue |
query tab=aktif + kondisi (semua tidak dikirim) |
string | Tidak |
| Tab Riwayat | _tabRiwayat |
query tab=riwayat |
string | Ya |
| Terima / Batalkan / Kembalikan | – | (tanpa body) | – | – |
28API → Mobile
| API JSON | Mobile Model (AssetPeminjamanRecord) |
UI |
|---|---|---|
id, no_trx |
id, noTrx | Navigasi, "Nomor Pengajuan" |
tgl_pengajuan / tgl_diterima |
tanggalPengajuan / tanggalDiterima | "Tanggal Pengajuan" / "Tanggal Diterima" |
keperluan, tujuan_peminjaman |
keperluan, tujuanPeminjaman | "Keperluan", "Tujuan Peminjaman" |
tgl_pinjam, tgl_kembali |
tanggalPinjam, tanggalKembali | "Tanggal Pinjam", "Tanggal Kembali" |
tgl_serah_terima, jam_serah_terima, keterangan_serah_terima |
idem | Info "Jadwal serah terima asset pada {tanggal}, jam {jam}." |
ket_tolak |
ketTolak | Alasan |
status_approval (+jenis_status) |
statusApproval (parseAssetPeminjamanApproval) |
Badge, tombol |
status_label, tab |
statusLabel, tab | Badge |
terlambat |
terlambat | "Terlambat" + peringatan |
dapat_dikembalikan |
dapatDikembalikan | Tombol Kembalikan Asset |
jenis_status, id_peminjaman_asal |
jenisStatus, idPeminjamanAsal | Mode pengembalian |
karyawan.* (termasuk karyawan.jabatan ← master_jabatan.nama) |
karyawan (jabatan default '') |
Data Karyawan: Jabatan, Cabang (cabang), Departemen (divisi) |
assets[] |
assets (AssetPeminjamanAsset) |
Daftar asset, keputusan/kondisi |
lokasi_gudang[], karyawan.id_cabang, assets[].nama_lokasi_tujuan |
AssetPeminjamanRecord.lokasiGudang, AssetPeminjamanKaryawan.idCabang, AssetPeminjamanAsset | Dropdown Lokasi Tujuan Asset; lokasi tujuan di kartu |
pengembalian[] (list riwayat: id,no_trx,tgl_selesai,jumlah_normal,jumlah_rusak; detail: + status_approval,status_label,tgl_pengajuan,assets[]) |
AssetPeminjamanRecord.pengembalian (AssetPeminjamanPengembalian, AssetPeminjamanPengembalianAsset) |
Kartu Riwayat (ringkasan), bagian Pengembalian di detail |
status_approval = dibatalkan |
AssetPeminjamanApproval.dibatalkan |
Badge Dibatalkan, chip Dibatalkan |
filters, records, pagination |
AssetPeminjamanPage | Chip, list, infinite scroll |
29Mapping Mobile → API → Database
| UI | Mobile Variable | JSON | Backend | Table | Column |
|---|---|---|---|---|---|
| Pilih Keperluan | keperluan |
keperluan |
trim | header | keperluan |
| Tujuan Peminjaman | tujuanPeminjaman |
tujuan_peminjaman |
trim | header | tujuan_peminjaman |
| Tanggal Pinjam | tanggalPinjam |
tgl_pinjam |
$tanggalPinjam |
header | tgl_peminjaman |
| Tanggal Kembali | tanggalKembali |
tgl_kembali |
$tanggalKembali |
header | tgl_dikembalikan |
| Tambah Asset | assetIds |
asset_ids[] |
$assets |
item / asset_data | id_asset_data (+snapshot) / status_asset = proses |
| (JWT karyawan) | – | Authorization | $karyawan |
header | id_karyawan, nik_karyawan, nama_karyawan, user_input |
| Setujui/Tolak | statusItem |
items[].status_item |
$statusItem |
item | status_item |
| Alasan Penolakan | ketTolak |
items[].ket_tolak |
$ketTolak |
item | ket_tolak |
| Jadwal | tanggalSerahTerima, jamSerahTerima |
tgl_serah_terima, jam_serah_terima |
Carbon | header | tgl_serah_terima, jam_serah_terima (H:i:s) |
| Catatan Tambahan | keteranganSerahTerima |
keterangan_serah_terima |
trim | header | keterangan_serah_terima |
| Tab / chip peminjaman | activePeminjamanTab |
query tab |
applyPeminjamanTabFilter |
header | status |
| Chip Jatuh Tempo / Terlambat | activePeminjamanKondisi |
query kondisi (hanya tab=aktif) |
applyKondisiAktifFilter |
header | tgl_dikembalikan |
| Batalkan | – | – | batalkanKaryawan |
asset_data / header | tersedia (proses+gudang) / status = dibatalkan, ket_tolak |
| Terima Asset | – | – | terimaKaryawan |
asset_data / header / asset_trx_log | dipinjam, pemegang karyawan / aktif / peminjaman_karyawan |
| Kembalikan Asset | – | – | ajukanPengembalianKaryawan |
header (baru) | jenis_status = pengembalian, id_peminjaman_asal |
| Normal/Rusak | kondisi |
items[].kondisi |
$condition |
item / asset_data | status_item (disetujui/ditolak) / status_asset (tersedia/rusak) |
| Keterangan Kerusakan | keteranganKerusakan |
items[].keterangan_kerusakan |
$damageNote |
item / asset_data | ket_tolak / ket_rusak |
| Lokasi Tujuan Asset | idLokasi |
items[].id_asset_lokasi_m |
resolveItemLocations |
item / asset_data | id_asset_lokasi_m_tujuan / id_asset_lokasi_m |
30Mapping Database → API → Mobile
| Table | Column | Backend | API JSON | Mobile | UI |
|---|---|---|---|---|---|
| header | tgl_input | dateOnly | tgl_pengajuan |
tanggalPengajuan | Tanggal Pengajuan |
| header | tgl_update (saat aktif) | dateOnly | tgl_diterima |
tanggalDiterima | Tanggal Diterima |
| header | tgl_peminjaman, tgl_dikembalikan | – | tgl_pinjam, tgl_kembali |
tanggalPinjam, tanggalKembali | Periode |
| header | tgl_dikembalikan + status | isTerlambat | terlambat |
terlambat | Terlambat |
| header | status + jenis_status | peminjamanStatusMetadata | status_approval, status_label, tab |
statusApproval, statusLabel, tab | Badge, tab |
| header + item + asset_data | – | getDetail | dapat_dikembalikan |
dapatDikembalikan | Tombol Kembalikan |
| item | COUNT | peminjamanQuery | jumlah_asset |
jumlahAsset | "{jumlah} Asset" |
| item | status_item (pengembalian dipetakan) | getDetail | assets[].status_item |
status | Disetujui/Ditolak Admin, Normal/Rusak |
| item | ket_tolak | getDetail | assets[].ket_tolak |
ketTolak | Alasan / Keterangan Kerusakan |
| asset_data | kode_asset, merek | LEFT JOIN | kode_asset, merek |
– | Kartu asset |
| header (jatuh tempo) | tgl_dikembalikan | getJatuhTempo | sisa_hari |
sisaHari | Banner jatuh tempo |
| header (pengembalian selesai) + item | tgl_approval, COUNT status_item | attachRiwayatPengembalian | pengembalian[].tgl_selesai, jumlah_normal, jumlah_rusak |
AssetPeminjamanPengembalian | Ringkasan kartu Riwayat |
| header (pengembalian) + item + asset_lokasi_m | status, tgl_input, status_item, ket_tolak, nama_lokasi | pengembalianForDetail | pengembalian[] (detail) |
AssetPeminjamanPengembalian(+Asset) | Bagian Pengembalian di detail |
| header | status = dibatalkan | peminjamanStatusMetadata | status_approval, status_label = Dibatalkan, tab = dibatalkan |
AssetPeminjamanApproval.dibatalkan | Badge Dibatalkan |
31Validation
31.1 Mobile
| Lokasi | Aturan | Pesan |
|---|---|---|
| Form | Min 1 asset | "Tambahkan minimal satu asset." |
| Form | Keperluan wajib | "Keperluan wajib dipilih." |
| Form | Tujuan wajib (trim) | "Tujuan peminjaman wajib diisi." |
| Form | Tanggal pinjam & kembali wajib; kembali ≥ pinjam | "Tanggal pinjam wajib dipilih." / "Tanggal kembali wajib dipilih." / "Tanggal kembali tidak boleh sebelum tanggal pinjam." |
| Form | Picker menyembunyikan asset yang sudah dipilih | – |
| Detail admin | Semua asset diputuskan; tolak wajib alasan | "Pilih keputusan untuk seluruh asset." / "Alasan penolakan wajib diisi." |
| Sheet admin | Tanggal & jam wajib bila ada disetujui; tanggal dibatasi periode pinjam | "Tanggal serah terima wajib dipilih." / "Jam serah terima wajib dipilih." |
| Detail pengembalian admin | Semua kondisi diisi; rusak wajib keterangan | "Pilih kondisi untuk seluruh asset." / "Keterangan kerusakan wajib diisi." |
| Detail pengembalian admin | Setiap asset wajib punya lokasi gudang (v1.1) | "Pilih lokasi gudang tujuan untuk seluruh asset." |
| Terima/Batalkan/Kembalikan | Dialog konfirmasi | – |
| API list | tab ∈ pengajuan, dikonfirmasi, ditolak, aktif, dibatalkan, riwayat; kondisi ∈ semua, jatuh_tempo, terlambat (v1.6) |
"Filter peminjaman asset tidak valid." / "Filter kondisi peminjaman asset tidak valid." |
31.2 Backend — lihat §9 dan §24.
31.3 Database
ENUM jenis_status, status (membutuhkan nilai dibatalkan dari SQL v1.6; pada MariaDB non-strict nilai enum tak dikenal tersimpan kosong, RISK-010), status_item; NOT NULL + default untuk tanggal (CURRENT_DATE, 0000-00-00) dan teks; no_trx tidak unique.
31.4 Ringkasan per Field
| Field | Mobile | Backend | Database |
|---|---|---|---|
| keperluan | Wajib, dari opsi | required ≤255 (tidak dicek terhadap opsi) | varchar(255) |
| tujuan_peminjaman | Wajib (tanpa batas panjang) | required ≤255 | varchar(255) |
| tgl_pinjam | Wajib | ≥ hari ini | date |
| tgl_kembali | Wajib, ≥ pinjam | ≥ tgl_pinjam | date |
| asset_ids | Min 1, tanpa duplikat | tersedia @gudang, tidak aktif | item |
| status_item (admin) | Semua dipilih | in disetujui/ditolak, lengkap | enum |
| jadwal serah terima | Dalam periode | wajib bila ada disetujui, dalam periode | date/time |
| kondisi (pengembalian) | Semua dipilih | in normal/rusak, lengkap | enum (disetujui/ditolak) |
32Error Handling
| Error | HTTP | Backend | Mobile |
|---|---|---|---|
| Field tidak valid | 422 | VALIDATION_ERROR + errors | Error field / snackbar |
| Aturan bisnis (termasuk Batalkan pada status tidak valid) | 422 | BUSINESS_RULE_VIOLATION | Snackbar error |
Parameter list tab/kondisi tidak valid |
422 | VALIDATION_ERROR: "Parameter daftar peminjaman asset tidak valid.", errors.tab "Filter peminjaman asset tidak valid.", errors.kondisi "Filter kondisi peminjaman asset tidak valid." (EN: "Invalid asset loan filter." / "Invalid asset loan condition filter.") |
Snackbar / halaman error + Coba Lagi |
| Detail tidak ditemukan | 404 | NOT_FOUND | Halaman error + Coba Lagi |
| Token | 401 | UNAUTHORIZED | Refresh & retry |
| Bukan admin | 403 | FORBIDDEN | Pesan error |
| Exception | 500 | SYSTEM_ERROR + log | Snackbar |
| Timeout / koneksi | – | – | "Tidak dapat terhubung ke server. Periksa koneksi Anda." |
| Double submit | – | tanpa idempotency | Bloc abaikan saat submitting; tombol loading |
| Notifikasi gagal | – | di-log | – |
33Audit Trail
| Data | Disimpan? | Lokasi |
|---|---|---|
| Pengaju & waktu | ✅ | user_input, tgl_input |
| Admin pemutus & waktu | ✅ | user_approval, tgl_approval |
| Keputusan per asset + alasan | ✅ | item status_item, ket_tolak |
| Waktu diterima karyawan | ⚠️ tidak langsung | tgl_update saat aktif (tertimpa bila ada update lain) |
| Pembatalan karyawan | ✅ (v1.6) | status dibatalkan + ket_tolak teks, user_update, tgl_update (data sebelum v1.6: ditolak + teks yang sama) |
| Serah terima fisik | ✅ | asset_trx_log peminjaman_karyawan |
| Pengembalian + kondisi | ✅ | item pengembalian + asset_trx_log pengembalian_karyawan |
| Histori status | ❌ | ⚠️ Belum ditemukan |
34Notification
| Event | Trigger | Penerima | Judul | Pesan | Rute mobile |
|---|---|---|---|---|---|
peminjaman.diajukan |
Ajukan | Semua admin | Pengajuan Peminjaman Asset | {nama} mengajukan peminjaman asset {no_trx}. | asset_admin_detail_peminjaman |
peminjaman.disetujui |
Konfirmasi (≥1 disetujui) | Karyawan | Peminjaman Asset Disetujui | Peminjaman asset {no_trx} telah dikonfirmasi admin. | asset_detail_peminjaman |
peminjaman.ditolak |
Konfirmasi (semua ditolak) | Karyawan | Peminjaman Asset Ditolak | Peminjaman asset {no_trx} ditolak admin. | asset_detail_peminjaman |
peminjaman.diterima |
Terima Asset | Semua admin | Asset Peminjaman Diterima | {nama} telah menerima asset peminjaman {no_trx}. | asset_admin_detail_peminjaman |
peminjaman.dibatalkan |
Batalkan (status pengajuan atau dikonfirmasi) | Semua admin | Peminjaman Asset Dibatalkan | {nama} membatalkan peminjaman asset {no_trx}. | asset_admin_detail_peminjaman |
pengembalian.diajukan |
Kembalikan Asset | Semua admin | Pengajuan Pengembalian Asset | {nama} mengajukan pengembalian asset {no_trx}. | asset_admin_detail_pengembalian |
pengembalian.dikonfirmasi |
Konfirmasi pengembalian | Karyawan | Pengembalian Asset Dikonfirmasi | Pengembalian asset {no_trx} telah dikonfirmasi admin. | asset_detail_pengembalian |
Pengingat jatuh tempo bukan notifikasi push: hanya daftar/banner (getJatuhTempo). ⚠️ Belum ditemukan: scheduler (Kernel::schedule kosong).
35Security
| Aspek | Implementasi |
|---|---|
| Autentikasi | Bearer JWT; context dari token |
| Isolasi company | Semua query id_company = ctx.id_company |
| Otorisasi admin | accessError + findAssetAdmin |
| Kepemilikan | Endpoint karyawan memfilter id_karyawan = ctx.id_karyawan; pengajuan selalu atas nama user login. Batalkan hanya untuk header jenis_status = peminjaman milik pemanggil; baris pengembalian atau milik orang lain → "Peminjaman asset tidak ditemukan." Tab Riwayat dan pengembalian[] hanya menempelkan pengembalian dengan id_karyawan dan company yang sama dengan peminjaman |
| Integritas asset | Lock company + lockForUpdate; cek status & pemegang asset di setiap langkah |
| Pemisahan offboarding | Pengembalian offboarding tidak bisa diproses di menu peminjaman |
| Catatan | Validasi request dijalankan sebelum cek admin (konfirmasi peminjaman/pengembalian) |
36Manual Book User/Admin
A. Karyawan — Meminjam Asset
- Buka Asset Saya → menu Peminjaman → tekan +.
- Pilih Asset → Tambah Asset → cari dan pilih asset → Pilih. Ulangi bila perlu.
- Di Form Peminjaman: Pilih Keperluan (Kantor/Dinas/Pelatihan/Lainnya), isi Tujuan Peminjaman, pilih Tanggal Pinjam dan Tanggal Kembali.
- Tekan Ajukan Peminjaman. Status Menunggu Konfirmasi.
- Salah mengajukan? Selama masih Menunggu Konfirmasi, buka detail transaksi (tab Permintaan Peminjaman, chip Pengajuan) → Batalkan → konfirmasi. Status menjadi Dibatalkan (chip Dibatalkan) dan asset kembali tersedia.
B. Admin — Menyetujui Peminjaman
- Asset Karyawan → aksi Peminjaman → tab Permintaan Peminjaman → chip Pengajuan → pilih transaksi.
- Untuk setiap asset pilih Setujui atau Tolak (isi Alasan Penolakan).
- Tekan Konfirmasi → isi Jadwal Serah Terima, Jam Serah Terima (dalam periode pinjam) dan Catatan Tambahan → Submit.
C. Karyawan — Menerima Asset
- Buka notifikasi Peminjaman Asset Disetujui atau banner di Asset Saya.
- Datang sesuai jadwal serah terima, periksa asset secara fisik.
- Tekan Terima Asset → konfirmasi. Status Aktif. - Tidak jadi meminjam: tekan Batalkan (tidak dapat dipulihkan). Status menjadi Dibatalkan, bukan Ditolak.
D. Karyawan — Mengembalikan Asset
- Perhatikan pengingat jatuh tempo di Asset Saya; lewat tanggal akan tertulis Terlambat. Tab Sedang Dipinjam dengan chip Jatuh Tempo / Terlambat menyaring peminjaman aktif.
- Buka detail peminjaman aktif → Kembalikan Asset → konfirmasi.
- Serahkan asset ke admin; pantau di tab Pengembalian. Setelah admin mengonfirmasi, peminjaman tampil di tab Riwayat Peminjaman beserta ringkasan pengembaliannya (mis. "Dikembalikan 28 Okt 2026 · 0 normal · 1 rusak").
E. Admin — Menerima Pengembalian
- Peminjaman → tab Pengembalian → chip Pengajuan → pilih transaksi.
- Periksa setiap asset: Normal atau Rusak (isi Keterangan Kerusakan).
- Pilih Lokasi Tujuan Asset (gudang) untuk setiap asset; default-nya gudang di cabang karyawan.
- Tekan Konfirmasi. Asset normal kembali tersedia, asset rusak berstatus rusak, keduanya di gudang yang dipilih; peminjaman otomatis selesai.
F. Melihat Riwayat Peminjaman (Karyawan & Admin)
- Buka tab Riwayat Peminjaman. Daftar berisi peminjaman yang sudah selesai (terbaru di atas); admin melihat seluruh karyawan di company, karyawan hanya miliknya.
- Kartu menampilkan nomor, periode, jumlah asset, badge Selesai, dan ringkasan pengembalian. Pengembalian yang berasal dari proses offboarding juga ikut dihitung di sini.
- Buka kartu untuk melihat detail; bagian Pengembalian merinci kondisi tiap asset. Detail selesai bersifat baca-saja bagi admin.
37Kondisi Button
| Button | Role | Halaman | Condition | Action |
|---|---|---|---|---|
| + | Karyawan | Peminjaman Asset | Selalu | Form peminjaman |
| Ajukan Peminjaman | Karyawan | Form | Tidak submitting | POST peminjaman |
| Batalkan | Karyawan | Detail | Status pengajuan (v1.6; tombol tunggal) |
Dialog → POST batalkan |
| Terima Asset / Batalkan | Karyawan | Detail | Status dikonfirmasi |
Dialog → POST terima / batalkan |
| Tab Permintaan / Sedang Dipinjam / Pengembalian / Riwayat | Karyawan, Admin | Peminjaman Asset | Selalu (v1.6; tanpa badge) | Ganti daftar; bar dapat digulir di layar sempit |
| Chip Pengajuan / Dikonfirmasi / Ditolak / Dibatalkan | Karyawan, Admin | Tab Permintaan Peminjaman | Selalu | tab=<chip> |
| Chip Semua / Jatuh Tempo / Terlambat | Karyawan, Admin | Tab Sedang Dipinjam | Selalu | tab=aktif (+ kondisi) |
| Kembalikan Asset | Karyawan | Detail | dapat_dikembalikan = true |
Dialog → POST pengembalian |
| Setujui / Tolak per asset | Admin | Detail peminjaman | Tab pengajuan |
Set keputusan |
| Konfirmasi | Admin | Detail peminjaman | Tab pengajuan |
Sheet jadwal → POST konfirmasi |
| Submit | Admin | Sheet | Jadwal valid bila ada disetujui | Kirim |
| Normal / Rusak per asset | Admin | Detail pengembalian | Tab pengajuan |
Set kondisi |
| Lokasi Tujuan Asset per asset | Admin | Detail pengembalian | Tab pengajuan |
Pilih gudang (wajib, v1.1) |
| Konfirmasi | Admin | Detail pengembalian | Tab pengajuan |
POST konfirmasi pengembalian |
| Tolak pengembalian / Perpanjang | – | – | ⚠️ Belum ditemukan | – |
38Testing Scenario
Happy Path
| TC | Scenario | Steps | Expected |
|---|---|---|---|
| TC-01 | Ajukan peminjaman | Isi form lengkap → Ajukan | 201; pengajuan; asset proses; notif admin |
| TC-02 | Setujui semua | Admin Setujui semua + jadwal dalam periode | dikonfirmasi; jadwal tersimpan; notif karyawan |
| TC-03 | Setujui sebagian | 1 setujui, 1 tolak + alasan | dikonfirmasi; asset ditolak tersedia; item ditolak tampil "Ditolak Admin" |
| TC-04 | Tolak semua | Semua tolak | ditolak, ket_tolak "Seluruh asset peminjaman ditolak."; asset tersedia |
| TC-05 | Terima asset | Karyawan Terima Asset | aktif; asset dipinjam @karyawan; asset_trx_log peminjaman_karyawan; notif admin |
| TC-06 | Batalkan saat dikonfirmasi | Karyawan Batalkan saat dikonfirmasi | dibatalkan (bukan ditolak), "Dibatalkan oleh karyawan."; asset disetujui tersedia; tidak ada log |
| TC-07 | Ajukan pengembalian | Kembalikan Asset | Header pengembalian pengajuan dengan id_peminjaman_asal; notif admin |
| TC-08 | Pengembalian normal | Admin Normal → Konfirmasi | Asset tersedia; log; pengembalian & peminjaman asal selesai |
| TC-09 | Pengembalian rusak | Rusak + keterangan | Asset rusak, ket_rusak; log kondisi_akhir rusak |
Validation
| TC | Scenario | Expected |
|---|---|---|
| TC-10 | Tanpa asset | "Tambahkan minimal satu asset." / 422 |
| TC-11 | Tanpa keperluan / tujuan | 422 |
| TC-12 | Tanggal pinjam kemarin (API) | 422 "Tanggal pinjam tidak boleh sebelum hari ini." |
| TC-13 | Tanggal kembali < pinjam | 422 |
| TC-14 | Asset tidak tersedia | 422 "…tidak tersedia untuk dipinjam." |
| TC-15 | Admin tidak memutuskan semua asset | 422 "Seluruh asset peminjaman wajib dikonfirmasi." |
| TC-16 | Tolak tanpa alasan | 422 |
| TC-17 | Jadwal di luar periode | 422 "Tanggal serah terima harus berada dalam periode peminjaman." |
| TC-18 | Ada disetujui tanpa jadwal | 422 "Tanggal serah terima wajib diisi." |
| TC-19 | Pengembalian tanpa semua kondisi | 422 |
| TC-20 | Rusak tanpa keterangan | 422 |
Status
| TC | Scenario | Expected |
|---|---|---|
| TC-21 | Konfirmasi ulang peminjaman | 422 "…sudah tidak menunggu konfirmasi." |
| TC-22 | Terima saat masih pengajuan | 422 "Peminjaman asset belum dapat diterima." |
| TC-23 | Batalkan saat aktif / selesai / ditolak / dibatalkan | 422 "…sudah tidak dapat dibatalkan." |
| TC-24 | Kembalikan saat belum aktif | 422 "Hanya peminjaman aktif yang dapat dikembalikan." |
| TC-25 | Ajukan pengembalian dua kali | 422 "Pengembalian asset sedang menunggu konfirmasi." |
| TC-26 | Konfirmasi pengembalian dua kali | 422 |
| TC-27 | Konfirmasi pengembalian milik offboarding | Tidak tampil; API 422 / 404 |
Asset / Jatuh Tempo
| TC | Scenario | Expected |
|---|---|---|
| TC-28 | Asset sedang dipinjam orang lain | Tidak muncul di opsi; API 422 |
| TC-29 | Asset sedang di onboarding | 422 |
| TC-30 | Tanggal kembali = besok | Muncul di pengingat jatuh tempo (sisa_hari = 1) |
| TC-31 | Lewat tanggal kembali | terlambat = true; label Terlambat; sisa_hari negatif |
| TC-32 | Sudah ajukan pengembalian saat jatuh tempo | Hilang dari pengingat |
API / Security / Network
| TC | Scenario | Expected |
|---|---|---|
| TC-33 | Tanpa token | 401 |
| TC-34 | Non-admin konfirmasi | 403 (atau 422 bila payload invalid) |
| TC-35 | Karyawan akses peminjaman orang lain | 404 / 422 |
| TC-36 | Double tap Terima | Hanya satu request; request kedua 422 |
| TC-37 | Timeout setelah commit ajukan | Ajukan ulang ditolak (asset proses) |
| TC-38 | Konfirmasi pengembalian dengan lokasi gudang berbeda per asset | asset_data.id_asset_lokasi_m dan item id_asset_lokasi_m_tujuan sesuai pilihan masing-masing; log tercatat; selesai |
| TC-39 | Konfirmasi pengembalian tanpa lokasi / lokasi tidak ada / lokasi bertipe ruangan | 422 "Lokasi gudang tujuan setiap asset wajib dipilih." atau "Lokasi gudang tujuan tidak valid."; status tetap pengajuan |
| TC-40 | Karyawan Terima Asset pinjaman (asset keluar gudang) | asset_data.id_asset_lokasi_m dikosongkan; status dipinjam @karyawan |
Pembatalan (v1.6)
| TC | Scenario | Expected |
|---|---|---|
| TC-41 | Batalkan saat pengajuan (sebelum admin memutuskan) |
200 "Peminjaman asset berhasil dibatalkan."; header dibatalkan, ket_tolak "Dibatalkan oleh karyawan."; semua asset item (status_asset proses) kembali tersedia @gudang; notif peminjaman.dibatalkan ke admin; tidak ada asset_trx_log |
| TC-42 | Batalkan saat dikonfirmasi dengan 1 disetujui + 1 ditolak |
Hanya asset disetujui yang diubah ke tersedia (asset ditolak sudah tersedia sejak konfirmasi); header dibatalkan |
| TC-43 | Batalkan saat asset item sudah bukan proses + gudang |
Asset itu tidak disentuh; header tetap dibatalkan |
| TC-44 | Batalkan peminjaman karyawan lain, id tidak ada, atau id baris pengembalian | 422 "Peminjaman asset tidak ditemukan."; tidak ada perubahan |
| TC-45 | Konfirmasi admin pada peminjaman yang sudah dibatalkan |
422 "Peminjaman asset sudah tidak menunggu konfirmasi." |
List: tab Dibatalkan, kondisi, Riwayat (v1.6)
| TC | Scenario | Expected |
|---|---|---|
| TC-46 | tab=dibatalkan (karyawan dan admin) |
Hanya header dibatalkan; filters memuat Pengajuan, Dikonfirmasi, Ditolak, Aktif, Dibatalkan |
| TC-47 | tab=aktif&kondisi=terlambat |
Hanya aktif dengan tgl_kembali < hari ini |
| TC-48 | tab=aktif&kondisi=jatuh_tempo |
Hanya aktif dengan tgl_kembali hari ini sampai besok; yang terlambat tidak muncul |
| TC-49 | tab=pengajuan&kondisi=terlambat |
kondisi diabaikan; daftar pengajuan biasa |
| TC-50 | kondisi=xyz |
422; errors.kondisi "Filter kondisi peminjaman asset tidak valid." (EN "Invalid asset loan condition filter.") |
| TC-51 | kondisi tanpa tab/limit/offset |
Respons array lama (non-paginasi); kondisi diabaikan |
| TC-52 | kondisi dengan limit kecil |
Filter diterapkan sebelum paginasi: has_more dan next_offset konsisten dengan hasil tersaring |
| TC-53 | tab=riwayat |
Hanya selesai, urut tgl_update desc lalu id desc; tiap item punya pengembalian[] (hanya selesai, id,no_trx,tgl_selesai,jumlah_normal,jumlah_rusak) |
| TC-54 | tab=riwayat dengan limit kecil |
limit+1: has_more benar, next_offset = offset + jumlah record |
| TC-55 | Pengembalian dari offboarding pada peminjaman selesai | Tampil di tab Riwayat dan detail peminjaman; tidak tampil di tab/list/detail Pengembalian |
| TC-56 | Riwayat karyawan vs admin | Karyawan hanya miliknya; admin seluruh company; tidak lintas company |
| TC-57 | Detail peminjaman | pengembalian[] berisi semua status (urut tgl_input asc) dengan assets[].kondisi/keterangan_kerusakan/lokasi_tujuan; kosong bila belum ada; tidak ada pada detail pengembalian |
Mobile (v1.6)
| TC | Scenario | Expected |
|---|---|---|
| TC-58 | Tab bar di layar 320 px | Empat tab dapat digulir horizontal dan tab terakhir terjangkau |
| TC-59 | Berpindah Permintaan → Sedang Dipinjam → Riwayat → kembali | Tidak ada data tab lain yang bocor; tab Permintaan kembali ke chip terakhir |
| TC-60 | Sedang Dipinjam: chip Terlambat, lalu Semua | Request tab=aktif&kondisi=terlambat; Semua mengirim tab=aktif tanpa kondisi. Tab lain tidak mengirim kondisi |
| TC-61 | Kartu Riwayat: 1 pengembalian, >1 pengembalian, tanpa pengembalian | "Dikembalikan {tgl} · N normal · M rusak"; dijumlah + tanggal terakhir + jumlah transaksi; ringkasan disembunyikan |
| TC-62 | Detail karyawan: pengajuan / dibatalkan | Pengajuan: hanya tombol Batalkan (tanpa Terima); Dibatalkan: badge Dibatalkan tanpa aksi |
| TC-63 | Detail admin peminjaman selesai |
Read-only (tanpa Setujui/Tolak/Konfirmasi), label dan warna Selesai, bagian Pengembalian tampil |
39Temuan Ketidaksesuaian
| ID | Component | Finding | Impact | Recommendation |
|---|---|---|---|---|
| INC-01 | DB ↔ Backend | Status pengembalian ditolak tidak pernah di-set; dikonfirmasi hanya dipakai pengembalian offboarding |
Enum membingungkan | Dokumentasikan / implementasi tolak pengembalian |
| INC-02 | DB | Enum item approval_item (disetujui/ditolak) dipakai ulang untuk kondisi pengembalian (normal/rusak); ket_tolak menyimpan keterangan kerusakan |
Makna kolom bergantung jenis baris | Kolom kondisi terpisah |
| INC-03 | Backend | ✅ Diperbaiki v1.6: pembatalan karyawan kini status dibatalkan. Sisa: data lama masih ditolak + teks "Dibatalkan oleh karyawan." |
Data lama tidak terbedakan dari penolakan admin kecuali teks; tab Dibatalkan tidak memuatnya | Jalankan SQL langkah 3 (opsional) |
| INC-04 | Backend ↔ Mobile | ✅ Diperbaiki v1.6: tab Riwayat Peminjaman menampilkan peminjaman selesai beserta pengembaliannya |
– | – |
| INC-05 | Mobile ↔ Backend | Field Tujuan Peminjaman tanpa batas panjang di mobile; backend maks 255 | Error 422 setelah submit | maxLength: 255 |
| INC-06 | Backend | keperluan tidak divalidasi terhadap daftar opsi |
Nilai bebas via API | in:kantor,dinas,pelatihan,lainnya |
| INC-07 | Backend | tgl_diterima diambil dari tgl_update |
Bisa bergeser jika header di-update | Kolom khusus |
| INC-08 | Backend | Validasi request sebelum cek admin | 422 vs 403 | Pindahkan cek akses |
| INC-09 | Backend | konfirmasiAdmin tidak memeriksa karyawan masih aktif (baru dicek saat terima) |
Asset tertahan bila karyawan nonaktif | Cek di konfirmasi |
| INC-10 | Backend ↔ Mobile | Chip Jatuh Tempo (kondisi=jatuh_tempo) hanya hari ini–besok dan tidak mengecualikan peminjaman yang pengembaliannya sedang diajukan; hitungan jatuh tempo dashboard/banner (jatuhTempoQuery) mencakup yang sudah lewat (≤ besok) dan mengecualikan yang pengembaliannya pengajuan/dikonfirmasi |
Jumlah chip dan angka dashboard bisa berbeda | Sepakati definisi tunggal atau beri label |
| INC-11 | Backend ↔ DB | Kode memakai nilai enum dibatalkan yang baru ada setelah SQL manual dijalankan; DBML sudah diperbarui, migrasi tidak ada |
Urutan deploy menentukan (RISK-010) | Jadikan migration atau cek otomatis sebelum rilis |
40Edge Case
| Edge Case | Current Behavior | Impact | Recommendation |
|---|---|---|---|
| Karyawan tidak pernah menekan Terima | Status dikonfirmasi selamanya, asset proses |
Asset terkunci | Kedaluwarsa otomatis / batal oleh admin |
| Karyawan ingin membatalkan sebelum admin memutuskan | ✅ Bisa (v1.6): batalkan pada status pengajuan → dibatalkan, semua asset item dilepas |
– | – |
| Karyawan membatalkan bersamaan dengan admin mengonfirmasi | Keduanya memakai lock company dan lock header; yang kedua gagal 422 ("…sudah tidak menunggu konfirmasi." / "…sudah tidak dapat dibatalkan.") | Aman | – |
| Data pembatalan sebelum v1.6 | Tersimpan ditolak + "Dibatalkan oleh karyawan."; tidak muncul di chip Dibatalkan |
Riwayat pembatalan terbagi dua status | SQL langkah 3 (opsional) |
| Peminjaman dengan pengembalian dari offboarding | Peminjaman selesai tampil di Riwayat dan detail memuat pengembalian offboarding; tab/list/detail Pengembalian tetap menyembunyikannya |
Keputusan produk | – |
Pengembalian selesai tanpa tgl_approval |
Kartu Riwayat menampilkan "Dikembalikan -" | Kosmetik | Fallback ke tgl_update |
| API v1.6 jalan sebelum SQL enum | Batalkan menulis dibatalkan ke kolom enum lama; MariaDB non-strict menyimpan string kosong, mode SQL strict menolak query |
Status rusak / 500 | Jalankan SQL dulu (RISK-010) |
| Admin tidak memproses pengembalian | Pengembalian pengajuan terus; asset tetap dipinjam; pengingat jatuh tempo hilang |
Asset lepas dari pantauan | SLA / pengingat admin |
| Pengembalian perlu ditolak (asset belum diserahkan) | Tidak ada aksi tolak | Admin terpaksa konfirmasi | Endpoint tolak |
| Tanggal pinjam sudah lewat saat admin konfirmasi | Jadwal tetap harus di dalam periode (bisa di masa lalu) | Jadwal tidak realistis | Validasi jadwal ≥ hari ini |
| Karyawan nonaktif saat meminjam | Pengembalian masih bisa diajukan & dikonfirmasi; offboarding juga membuat pengembalian | – | – |
| Asset dipinjam lalu karyawan offboarding | Offboarding membuat pengembalian anak (relasi) yang disembunyikan dari menu peminjaman | Aman | – |
| Pengembalian sebagian asset | Tidak didukung (semua asset ikut) | Karyawan harus mengembalikan semua | Pilihan item |
| Perpanjangan peminjaman | Tidak ada | Harus ajukan ulang | Fitur perpanjangan |
| Dua karyawan mengajukan asset sama bersamaan | Lock company; kedua 422 | Aman | – |
41Potential Bug / Technical Risk
RISK-001 — Asset terkunci bila peminjaman tidak pernah diterima (TINGGI)
Lokasi: konfirmasiAdmin, terimaKaryawan, batalkanKaryawan.
Masalah: Setelah dikonfirmasi, hanya karyawan yang bisa menerima/membatalkan. Tidak ada kedaluwarsa atau aksi admin.
Impact: Asset disetujui tetap proses tanpa batas waktu, tidak bisa dipinjam/diberikan ke orang lain.
Recommendation: Pembatalan oleh admin dan/atau kedaluwarsa otomatis (mis. lewat tgl_dikembalikan).
RISK-002 — Pengajuan tidak bisa dibatalkan karyawan (SEDANG) — ✅ DITUTUP v1.6
Karyawan kini dapat membatalkan pada status pengajuan dan dikonfirmasi; asset yang dikunci dilepas (proses → tersedia) dan status menjadi dibatalkan. Syarat deploy: SQL enum dibatalkan (RISK-010).
RISK-003 — Tidak ada pengingat otomatis (SEDANG)
Kernel::schedule kosong; pengingat jatuh tempo hanya muncul ketika aplikasi dibuka. Tidak ada push/notif H-1 atau terlambat.
RISK-004 — Pengembalian tidak bisa ditolak/dibatalkan (SEDANG)
Pengembalian yang salah diajukan tidak bisa dibatalkan karyawan atau ditolak admin; admin harus mengonfirmasi (asset otomatis masuk gudang).
RISK-005 — Pengembalian wajib seluruh asset (RENDAH)
Tidak bisa mengembalikan sebagian; desain atau keterbatasan perlu dikonfirmasi bisnis.
RISK-006 — Tanggal nol 0000-00-00 (SEDANG)
Insert memakai 0000-00-00/00:00:00; ⚠️ Asumsi: gagal pada MySQL strict mode NO_ZERO_DATE.
RISK-007 — Tanpa batas durasi pinjam (RENDAH)
tgl_kembali bebas selama ≥ tgl_pinjam.
RISK-008 — Kapasitas penomoran (RENDAH)
3 digit/bulan/company dipakai bersama peminjaman & pengembalian (header) serta item.
RISK-009 — Notifikasi di luar transaksi (RENDAH)
Kegagalan notif hanya di-log.
RISK-010 — Urutan deploy: SQL enum harus sebelum API v1.6 (TINGGI)
Lokasi: batalkanKaryawan menulis status = 'dibatalkan'; filter tab=dibatalkan.
Masalah: Nilai dibatalkan hanya ada di kolom setelah sql-peminjaman-status-dibatalkan.sql dijalankan. Bila API v1.6 dipakai lebih dulu, pada MariaDB non-strict nilai enum tak dikenal tersimpan sebagai string kosong (''), sedangkan mode strict menolak query. Status eksekusi SQL di server tidak dapat diverifikasi dari kode.
Impact: Peminjaman yang dibatalkan tidak bisa dikenali statusnya (asset sudah dilepas, header tidak terbaca sebagai dibatalkan).
Recommendation: Jalankan SQL (langkah 0–2) lebih dulu dan verifikasi COLUMN_TYPE; pertimbangkan migration atau pemeriksaan pra-rilis.
RISK-011 — Data pembatalan lama tetap ditolak (RENDAH)
Pembatalan sebelum v1.6 tersimpan ditolak + "Dibatalkan oleh karyawan.". Tab Dibatalkan hanya memuat status baru; SQL langkah 3 bersifat opsional.
RISK-012 — Detail peminjaman menjalankan beberapa query per pengembalian (RENDAH)
pengembalianForDetail memanggil getDetail per transaksi pengembalian (sedikitnya query header dan item per pengembalian, selain satu query daftar). Jumlah pengembalian per peminjaman umumnya kecil (satu pengembalian menutup seluruh asset), tetapi pola ini N+1 bila bertambah.
RISK-013 — Kartu Riwayat "Dikembalikan -" (RENDAH)
Bila tgl_approval pengembalian kosong, tgl_selesai bernilai "" dan mobile menampilkan "-" pada ringkasan "Dikembalikan {tgl}".
RISK-014 — Definisi jatuh tempo berbeda antara chip dan dashboard (RENDAH)
Lihat INC-10: chip tidak mengecualikan peminjaman yang pengembaliannya sedang diajukan dan tidak memasukkan yang sudah lewat; dashboard sebaliknya.
42Dependency
Master Company / Data Karyawan / Asset Admin
↓
Asset Data (tersedia @gudang)
↓
Peminjaman (pengajuan → dikonfirmasi → aktif) ──► Notifikasi
↓ Terima Asset → asset_trx_log peminjaman_karyawan
Asset Data (dipinjam @karyawan) ──► Jatuh tempo (Asset Saya, Summary Admin)
↓ Pengembalian (header baru)
Asset Data (tersedia / rusak @gudang) ──► asset_trx_log pengembalian_karyawan
Terkait:
- Onboarding & Request: hasActiveOnboardingAsset dipakai saat pengajuan
- Offboarding: membuat pengembalian anak (asset_trx_offboarding_relasi); hasActiveOffboardingAsset mencegah pengembalian ganda
- Asset Saya & Summary Admin: getPendingForEmployee, getPendingForAdmin, getJatuhTempo, getPeminjamanPage (aktif)
- SQL manual `sql-peminjaman-status-dibatalkan.sql` (enum `dibatalkan`) harus dijalankan sebelum API v1.6
43Source Code Reference
| File | Layer | Function |
|---|---|---|
API/routes/modules/asset/RouteAsset.php |
Route | 15 endpoint |
API/app/Http/Controllers/Asset/Admin/AssetAdminPeminjaman_c.php, API/app/Http/Controllers/Asset/Admin/AssetAdminPengembalian_c.php |
Controller | Admin peminjaman & pengembalian |
API/app/Http/Controllers/Asset/Karyawan/AssetKaryawanPeminjaman_c.php |
Controller | Karyawan + prosesSerahTerimaPeminjaman |
API/app/Models/Asset/AssetPeminjamanKaryawan_m.php |
Model | Seluruh logika |
API/app/Models/Asset/PeminjamanStatus.php |
Enum | Status |
API/app/Models/Asset/Asset_m.php |
Model | Helper bersama |
API/app/Models/Asset/AssetOffboardingKaryawan_m.php |
Model | Pengembalian via offboarding, hasActiveOffboardingAsset |
API/app/Models/Asset/AssetKaryawan_m.php, AssetAdmin_m.php |
Model | Asset Saya, summary |
API/app/Support/Asset/AssetNotification.php |
Support | Event peminjaman.*, pengembalian.* |
API/diagram-asset.dbml; migrations 2026_09_17_000006, 2026_09_17_000009 |
DB | Struktur, index, enum (status_peminjaman + dibatalkan bertanda [NEW KRY05]) |
Claude outputs/sql-peminjaman-status-dibatalkan.sql |
SQL manual | ALTER … MODIFY COLUMN status + verifikasi + opsi merapikan data lama + rollback |
API/routes/modules/asset/README.md |
Dokumentasi API | Kontrak list/detail/batalkan v1.6 |
API/tests/Asset/AssetModelTest.php, AssetPeminjamanLocaleTest.php |
Test API | Batal pengajuan/dikonfirmasi, kondisi, riwayat, pengembalian offboarding, validasi locale |
MOBILE/test/asset/asset_peminjaman_test.dart, asset_peminjaman_admin_test.dart |
Test mobile | 4 tab, kondisi, riwayat, detail pengembalian, 320 px |
MOBILE/lib/Repository/Model/PageModel/mAssetPage.dart |
i18n model | Kunci lokalisasi baru |
MOBILE/lib/Repository/Service/assetPeminjamanApi.dart, assetPeminjamanAdminApi.dart |
Api | Endpoint |
MOBILE/lib/Repository/Model/Asset/mAssetPeminjaman.dart, mAssetPeminjamanAdmin.dart |
Model | – |
MOBILE/lib/Bloc/Asset/AssetPeminjamanBloc.dart, AssetPeminjamanAdminBloc.dart |
Bloc | – |
MOBILE/lib/UI/Modul/Asset/AssetSaya/Peminjaman/* |
UI karyawan | – |
MOBILE/lib/UI/Modul/Asset/AssetAdmin/AssetAdminKaryawan/Peminjaman/* |
UI admin | – |
MOBILE/lib/UI/Modul/Asset/AssetSaya/HalamanAssetSaya.dart |
UI | Banner & kartu |
MOBILE/lib/Setting/router.dart, Bloc/NavigatorBloc.dart |
Navigasi | – |
MOBILE/assets/id/asset.json |
i18n | Label |
44Developer Notes
- Satu tabel dua jenis baris: selalu filter
jenis_status. Pengembalian adalah header baru, bukan update status peminjaman. - Peminjaman asal ditutup otomatis saat tidak ada lagi asset pinjaman yang dipegang karyawan.
- Makna
status_itembergantung jenis baris;getDetailmemetakandisetujui/ditolak→normal/rusakuntuk pengembalian. Pertahankan pemetaan ini bila mengubah enum. - Pengembalian offboarding ditandai lewat
asset_trx_offboarding_relasidan selalu dikecualikan dari list/detail/konfirmasi peminjaman (cekSchema::hasTablesebelum dipakai). - List peminjaman menyembunyikan
aktifyang asetnya sudah tidak dipegang karyawan (mis. sudah dikembalikan lewat jalur lain). - Jadwal serah terima divalidasi di model (bukan di request rule) terhadap periode pinjam.
sisa_haridihitungtoday → tgl_dikembalikan(negatif = terlambat); pengingat mulai H-1.- Konstanta status memakai
PeminjamanStatusenum (konsisten), berbeda dengan modul Request. - Status
dibatalkan(v1.6): hanya untuk barisjenis_status = peminjaman; deploy SQL enum lebih dulu. Hanya assetproses+ gudang yang dilepas, tanpaasset_trx_log. - List peminjaman paginasi:
limit+1tanpa COUNT;kondisihanya untuktab=aktif; tabriwayatmemakaireorder()ketgl_updatedesc dan tidak ada difiltersstatis. - Riwayat & detail:
pengembalian[]ditempel dari query terpisah (satu query per halaman di list,getDetailper pengembalian di detail). Pengembalian offboarding sengaja ikut (keputusan produk). - Mobile:
AssetPeminjamanPageModekini empat nilai; pemanggil lama dipetakan di_assetPeminjamanModeFrom. State daftar per tab dijaga_listMatchesMode. - Technical debt: RISK-001, 003, 004, 010, INC-02/07/10.
45End-to-End Traceability
45.1 Tanggal kembali → pengingat
Karyawan memilih rentang tanggal di Form Peminjaman
→ _returnDate → AssetPeminjamanRequest.tanggalKembali
→ JSON "tgl_kembali" (after_or_equal:tgl_pinjam)
→ store() → asset_trx_peminjaman_karyawan.tgl_dikembalikan
→ getJatuhTempo(): tgl_dikembalikan ≤ besok → "sisa_hari"
→ Asset Saya: banner jatuh tempo; detail: "terlambat" → label "Terlambat"
45.2 Asset → dipinjam → kembali
Karyawan memilih asset (Tambah Pinjam Asset) → asset_ids
→ store(): item + asset_data.status_asset = proses
Admin: Setujui → items[].status_item = disetujui → item.status_item
Karyawan: Terima Asset → terimaKaryawan()
→ asset_data: dipinjam, pemegang_asset = karyawan, id_pemegang_asset = id_karyawan
→ asset_trx_log (peminjaman_karyawan)
Karyawan: Kembalikan Asset → header pengembalian (id_peminjaman_asal)
Admin: Normal → items[].kondisi = normal → item.status_item = disetujui
→ asset_data: tersedia, pemegang_asset = gudang, id_asset_lokasi_m = items[].id_asset_lokasi_m
→ asset_trx_log (pengembalian_karyawan) → peminjaman asal: selesai
45.3 DB → UI
asset_trx_peminjaman_karyawan_item.ket_tolak (baris pengembalian)
→ getDetail() → "assets[].ket_tolak" (status_item = rusak)
→ AssetPeminjamanAsset → "Keterangan Kerusakan" di Detail Pengembalian
45.4 Walkthrough Lengkap dengan Contoh Data
Bagian ini mengikuti satu peminjaman dari awal sampai asset kembali, langkah demi langkah: apa yang dilakukan pengguna di mobile, apa yang dikirim, apa yang diproses API, dan apa yang tersimpan di database. Data bersifat sintetis, tetapi format ID, nama kolom, urutan proses, label status, dan pesan diambil dari kode (AssetPeminjamanKaryawan_m, AssetKaryawanPeminjaman_c, AssetAdminPeminjaman_c, AssetAdminPengembalian_c, AssetNotification).
Skenario: Andi mengajukan pinjaman proyektor dan layar untuk presentasi dinas. Admin menyetujui proyektor dan menolak layar. Andi menerima proyektor, lalu mengembalikannya dalam kondisi rusak.
| Langkah | Pelaku | Aksi | Endpoint |
|---|---|---|---|
| 0 | Karyawan | Membuka form, memuat opsi | API-007 GET /asset/karyawan/peminjaman/options |
| 1 | Karyawan | Ajukan Peminjaman (2 asset) | API-009 POST /asset/karyawan/peminjaman |
| 2 | Admin | Setujui 1 asset, tolak 1 asset, isi jadwal, Submit | API-003 POST /asset/peminjaman-karyawan/{id}/konfirmasi |
| 3 | Karyawan | Terima Asset | API-011 POST /asset/karyawan/peminjaman/{id}/terima |
| 4 | Karyawan | Kembalikan Asset | API-013 POST /asset/karyawan/peminjaman/{id}/pengembalian |
| 5 | Admin | Periksa kondisi (Rusak), pilih gudang, Konfirmasi | API-006 POST /asset/pengembalian-karyawan/{id}/konfirmasi |
Data awal
Company CMP001. Pengajuan 22 Oktober 2026, periode ID 202610. Belum ada transaksi peminjaman, log, atau notifikasi pada periode ini.
data_karyawan
| id_karyawan | nik | nama_lengkap | Peran |
|---|---|---|---|
| KRY0003 | EMP-003 | Andi Contoh | Peminjam |
| KRY0099 | ADM-001 | Siti Admin | Admin Asset (aktor) |
| KRY0100 | ADM-002 | Rudi Admin | Admin Asset lain (ikut menerima notifikasi) |
asset_data (status_asset = tersedia, pemegang_asset = gudang, id_pemegang_asset = '', kondisi_awal = normal, id_asset_lokasi_m = CMP001202601001 = Gudang Pusat)
| Kode | id_asset_data | nama_asset | kategori | deskripsi |
|---|---|---|---|---|
| A9 | CMP00120260100009 (PRJ-0002) | Proyektor Mini | Proyektor | Kabel HDMI, tas |
| A10 | CMP00120260100010 (LYR-0001) | Layar Portable | Layar | Tripod |
Lokasi gudang untuk pengembalian: CMP001202610001 = Gudang Servis (asset_lokasi_m, tipe gudang, company sama).
Penomoran: header dan item id_company + YYYYMM + 3 digit, no_trx = KRY05-YYYYMMNNN. Pengajuan pengembalian memakai urutan yang sama dengan peminjaman (satu tabel, jenis_status berbeda). Log dan notifikasi 5 digit.
Langkah 0 — Karyawan membuka form
Mobile: Asset Saya → Peminjaman → + → /asset_tambah_peminjaman → LoadAssetPeminjamanOptions → AssetPeminjamanApi.getOptions().
Dikirim
GET /asset/karyawan/peminjaman/options
Authorization: Bearer <access_token_andi>
Diproses API: jwt.api + jwt.ctx, lalu AssetPeminjamanKaryawan_m::getOptions: asset tersedia di gudang yang tidak ada di peminjaman berjalan (item yang ditolak tidak mengunci), ditambah daftar keperluan tetap.
Database: hanya SELECT.
Diterima mobile: keperluan[] (kantor, dinas, pelatihan, lainnya) dan assets[] (A9, A10) seperti API-007. Dipakai pada bottom sheet Tambah Pinjam Asset dan Pilih Keperluan.
Langkah 1 — Andi mengajukan peminjaman
Mobile: Andi memilih kedua asset, keperluan Dinas, tujuan "Presentasi klien di Surabaya", dan rentang tanggal 24–28 Okt 2026, lalu Ajukan Peminjaman → AssetPeminjamanApi.store(AssetPeminjamanRequest).
Dikirim
POST /asset/karyawan/peminjaman
{
"keperluan": "dinas",
"tujuan_peminjaman": "Presentasi klien di Surabaya",
"tgl_pinjam": "2026-10-24",
"tgl_kembali": "2026-10-28",
"asset_ids": ["CMP00120260100009", "CMP00120260100010"]
}
id_karyawan tidak dikirim: diambil dari JWT.
Diproses API (AssetKaryawanPeminjaman_c::storePeminjamanSaya → AssetPeminjamanKaryawan_m::store, satu DB::transaction)
1. Validasi: keperluan dan tujuan_peminjaman wajib (maks 255); tgl_pinjam format Y-m-d dan tidak boleh sebelum hari ini; tgl_kembali tidak boleh sebelum tgl_pinjam; asset_ids minimal 1, unik.
2. lockCompany("CMP001"); findActiveKaryawan("KRY0003").
3. getLockedAssets: A9 dan A10 harus tersedia + gudang (FOR UPDATE).
4. hasActiveOnboardingAsset dan hasActivePeminjamanAsset (peminjaman pengajuan/dikonfirmasi/aktif dengan item tidak ditolak): lolos.
5. Generate ID: header urutan 1 → CMP001202610001, no_trx = KRY05-202610001; item mulai urutan 1.
6. INSERT header (status = pengajuan, jadwal kosong), INSERT item (status_item = NULL), UPDATE asset_data → proses.
7. Setelah commit: AssetNotification::send('peminjaman.diajukan', …) ke seluruh admin.
Hasil database
asset_trx_peminjaman_karyawan
| Kolom | Nilai |
|---|---|
| id_asset_trx_peminjaman_karyawan | CMP001202610001 |
| no_trx | KRY05-202610001 |
| tgl_peminjaman / tgl_dikembalikan | 2026-10-24 / 2026-10-28 |
| keperluan / tujuan_peminjaman | dinas / Presentasi klien di Surabaya |
| tgl_serah_terima / jam_serah_terima / keterangan_serah_terima | 0000-00-00 / 00:00:00 / '' |
| ket_tolak | '' |
| id_karyawan / nik_karyawan / nama_karyawan | KRY0003 / EMP-003 / Andi Contoh |
| jenis_status | peminjaman |
| id_peminjaman_asal | NULL |
| status | pengajuan |
| user_approval / tgl_approval | '' / NULL |
| tgl_input / user_input | 2026-10-22 09:00:00 / EMP-003 - Andi Contoh |
| tgl_update / user_update | NULL / '' |
asset_trx_peminjaman_karyawan_item
| id_asset_trx_peminjaman_item | id_asset_trx_peminjaman | id_asset_data | nama_asset | deskripsi_asset | kategori_asset | status_item | ket_tolak |
|---|---|---|---|---|---|---|---|
| CMP001202610001 | CMP001202610001 | A9 | Proyektor Mini | Kabel HDMI, tas | Proyektor | NULL | '' |
| CMP001202610002 | CMP001202610001 | A10 | Layar Portable | Tripod | Layar | NULL | '' |
asset_data: A9 dan A10 status_asset tersedia → proses, tgl_update 09:00:00, user_update = EMP-003 - Andi Contoh. Peminjam hanya mengubah status; pemegang_asset tetap gudang.
notif_data (2 baris, satu per admin)
| id_notif | id_karyawan | id_ref | rute | ket_notif_id |
|---|---|---|---|---|
| CMP00120261000001 | KRY0099 | CMP001202610001 | asset_admin_detail_peminjaman | Andi Contoh mengajukan peminjaman asset KRY05-202610001. |
| CMP00120261000002 | KRY0100 | CMP001202610001 | asset_admin_detail_peminjaman | Andi Contoh mengajukan peminjaman asset KRY05-202610001. |
jenis = asset, flag_baca = 0; teks Inggris ada di ket_notif_en.
Diterima mobile (HTTP 201)
{ "code": "CREATED", "message": "Pengajuan peminjaman asset berhasil dikirim.",
"data": { "id": "CMP001202610001", "no_trx": "KRY05-202610001", "status_approval": "pengajuan",
"status_label": "Menunggu Konfirmasi", "tab": "pengajuan" } }
Langkah 2 — Admin menyetujui satu asset dan menolak satu asset
Mobile: notifikasi membuka /asset_admin_detail_peminjaman → LoadAssetPeminjamanAdminDetail(id) (GET /asset/peminjaman-karyawan/CMP001202610001). Admin menekan Setujui pada proyektor, Tolak pada layar dengan alasan, lalu Konfirmasi → sheet Konfirmasi Peminjaman Asset (tanggal serah terima dibatasi periode pinjam) → Submit → AssetPeminjamanAdminApi.konfirmasi.
Dikirim
POST /asset/peminjaman-karyawan/CMP001202610001/konfirmasi
{
"items": [
{ "id_item": "CMP001202610001", "status_item": "disetujui" },
{ "id_item": "CMP001202610002", "status_item": "ditolak", "ket_tolak": "Sedang dijadwalkan servis" }
],
"tgl_serah_terima": "2026-10-24",
"jam_serah_terima": "08:30",
"keterangan_serah_terima": "Ambil di ruang IT"
}
Diproses API (AssetAdminPeminjaman_c::konfirmasiPeminjamanKaryawan → konfirmasiAdmin)
1. Validasi items (id unik, status_item ∈ disetujui/ditolak, ket_tolak wajib bila ditolak); format tanggal Y-m-d dan jam H:i. Cek admin.
2. lockCompany; header dikunci FOR UPDATE; harus berstatus pengajuan dan bukan milik offboarding (tidak ada relasi di asset_trx_offboarding_relasi).
3. Seluruh item harus diputuskan ("Seluruh asset peminjaman wajib dikonfirmasi.").
4. UPDATE tiap item: status_item, ket_tolak (kosong untuk disetujui).
5. Karena ada yang disetujui: tanggal dan jam serah terima wajib, formatnya harus valid, dan tanggalnya harus di dalam periode pinjam (24–28 Okt) — 24 Okt lolos.
6. UPDATE header: status = dikonfirmasi, jadwal, ket_tolak = '', user_approval, tgl_approval, tgl_update, user_update.
7. Asset ditolak (A10) dikembalikan: tersedia, gudang, id_pemegang_asset = ''. Asset disetujui (A9) tidak disentuh, tetap proses.
8. Notifikasi peminjaman.disetujui ke peminjam.
Hasil database
| Tabel | Perubahan |
|---|---|
item …001 (A9) |
status_item NULL → disetujui, ket_tolak '' |
item …002 (A10) |
status_item NULL → ditolak, ket_tolak = Sedang dijadwalkan servis |
| header | status pengajuan → dikonfirmasi; tgl_serah_terima 2026-10-24; jam_serah_terima 08:30:00; keterangan_serah_terima = Ambil di ruang IT; user_approval = ADM-001 - Siti Admin; tgl_approval = 2026-10-22 10:00:00; tgl_update/user_update sama |
| asset_data A10 | status_asset proses → tersedia; pemegang_asset gudang; id_pemegang_asset '' |
| asset_data A9 | Tidak berubah (proses) |
| notif_data | + CMP00120261000003: id_karyawan KRY0003, id_ref CMP001202610001, rute asset_detail_peminjaman, "Peminjaman asset KRY05-202610001 telah dikonfirmasi admin." |
Diterima mobile (HTTP 200)
{ "code": "UPDATED", "message": "Peminjaman asset berhasil dikonfirmasi.",
"data": { "id": "CMP001202610001", "no_trx": "KRY05-202610001", "status_approval": "dikonfirmasi",
"status_label": "Dikonfirmasi", "tab": "dikonfirmasi", "jumlah_disetujui": 1, "jumlah_ditolak": 1 } }
Langkah 3 — Andi menerima asset
Mobile: banner di Asset Saya atau notifikasi → /asset_detail_peminjaman. Detail (API-010) menampilkan proyektor Disetujui Admin, layar Ditolak Admin + alasan, jadwal serah terima, dan panel "Siap untuk meminjam Asset?" dengan tombol Batalkan dan Terima Asset. Andi menekan Terima Asset (dialog konfirmasi) → AssetPeminjamanApi.terima(id).
Dikirim
POST /asset/karyawan/peminjaman/CMP001202610001/terima
Tanpa body.
Diproses API (prosesSerahTerimaPeminjaman → AssetPeminjamanKaryawan_m::terimaKaryawan)
1. lockCompany; header milik karyawan (FOR UPDATE), status harus dikonfirmasi ("Peminjaman asset belum dapat diterima.").
2. Karyawan aktif; item berstatus disetujui = hanya …001.
3. Asset-nya dikunci dan harus proses + gudang ("Satu atau beberapa asset belum siap diserahterimakan.").
4. UPDATE asset_data A9 → dipinjam, karyawan, id_pemegang_asset = KRY0003, id_asset_lokasi_m = NULL (asset keluar dari gudang).
5. UPDATE header → aktif (tgl_update menjadi tanggal diterima).
6. INSERT asset_trx_log (jenis_log = peminjaman_karyawan) per asset.
7. Notifikasi peminjaman.diterima ke admin.
Hasil database
| Tabel | Perubahan |
|---|---|
| asset_data A9 | status_asset proses → dipinjam; pemegang_asset gudang → karyawan; id_pemegang_asset '' → KRY0003; id_asset_lokasi_m CMP001202601001 → NULL; tgl_update 2026-10-24 08:45:00; user_update = EMP-003 - Andi Contoh |
| header | status dikonfirmasi → aktif; tgl_update = 2026-10-24 08:45:00; user_update = EMP-003 - Andi Contoh |
asset_trx_log (1 baris)
| Kolom | Nilai |
|---|---|
| id_asset_trx_log | CMP00120261000001 |
| id_asset_data | CMP00120260100009 |
| jenis_log | peminjaman_karyawan |
| id_transaksi / nama_tabel_transaksi | CMP001202610001 / asset_trx_peminjaman_karyawan |
| kondisi_awal / kondisi_akhir | normal / normal |
| posisi_awal / posisi_akhir | gudang / karyawan |
| id_pemegang / nama_pemegang | KRY0003 / Andi Contoh |
| user_input | EMP-003 - Andi Contoh |
notif_data (2 baris ke admin): CMP00120261000004 (KRY0099) dan CMP00120261000005 (KRY0100), rute asset_admin_detail_peminjaman, "Andi Contoh telah menerima asset peminjaman KRY05-202610001."
Diterima mobile (HTTP 200)
{ "code": "UPDATED", "message": "Asset peminjaman berhasil diterima.",
"data": { "id": "CMP001202610001", "no_trx": "KRY05-202610001", "status_approval": "aktif",
"status_label": "Aktif", "tab": "aktif", "jumlah_asset": 1 } }
Selama aktif, Asset Saya menampilkan kartu peminjaman aktif. Pada H-1 sampai tgl_dikembalikan muncul pengingat jatuh tempo (sisa_hari), dan setelah lewat tanggal itu label menjadi Terlambat. Pengingat dihitung saat data dibaca; tidak ada scheduler atau push otomatis.
Langkah 4 — Andi mengajukan pengembalian
Mobile: di detail peminjaman aktif (dapat_dikembalikan = true), Kembalikan Asset (dialog "Asset aktif pada peminjaman ini akan diajukan untuk dikembalikan.") → AssetPeminjamanApi.ajukanPengembalian(id).
Dikirim
POST /asset/karyawan/peminjaman/CMP001202610001/pengembalian
Tanpa body: seluruh asset yang disetujui ikut dikembalikan.
Diproses API (ajukanPengembalianKaryawan)
1. lockCompany; peminjaman milik karyawan harus aktif.
2. Item disetujui = …001; semua asset harus masih dipinjam oleh karyawan ini.
3. Tidak boleh ada pengembalian pengajuan/dikonfirmasi untuk asset itu (hasActivePengembalianAsset), dan asset tidak boleh sedang di offboarding (AssetOffboardingKaryawan_m::hasActiveOffboardingAsset).
4. Generate ID dengan urutan yang sama dengan peminjaman: header urutan 2 → CMP001202610002, no_trx = KRY05-202610002; item urutan 3.
5. INSERT header jenis_status = pengembalian dengan id_peminjaman_asal, periode/keperluan/tujuan disalin dari peminjaman, status = pengajuan; INSERT item (salinan item disetujui, status_item = NULL).
6. asset_data tidak diubah (A9 tetap dipinjam).
7. Notifikasi pengembalian.diajukan ke admin (memakai ID pengembalian).
Hasil database
Header baru CMP001202610002
| Kolom | Nilai |
|---|---|
| no_trx | KRY05-202610002 |
| jenis_status / id_peminjaman_asal | pengembalian / CMP001202610001 |
| tgl_peminjaman / tgl_dikembalikan / keperluan / tujuan_peminjaman | disalin dari peminjaman asal |
| tgl_serah_terima / jam_serah_terima | 0000-00-00 / 00:00:00 |
| id_karyawan / nik / nama | KRY0003 / EMP-003 / Andi Contoh |
| status | pengajuan |
| tgl_input / user_input | 2026-10-28 16:00:00 / EMP-003 - Andi Contoh |
Item baru: CMP001202610003, id_asset_trx_peminjaman = CMP001202610002, A9, Proyektor Mini, status_item = NULL, id_asset_lokasi_m_tujuan = ''.
notif_data: CMP00120261000006 (KRY0099) dan CMP00120261000007 (KRY0100), id_ref CMP001202610002, rute asset_admin_detail_pengembalian, "Andi Contoh mengajukan pengembalian asset KRY05-202610002."
Diterima mobile (HTTP 201)
{ "code": "CREATED", "message": "Pengajuan pengembalian asset berhasil dikirim.",
"data": { "id": "CMP001202610002", "no_trx": "KRY05-202610002", "jenis_status": "pengembalian",
"id_peminjaman_asal": "CMP001202610001", "status_approval": "pengajuan",
"status_label": "Menunggu Konfirmasi", "tab": "pengajuan", "jumlah_asset": 1 } }
Tombol Kembalikan Asset hilang karena sudah ada pengembalian berjalan.
Langkah 5 — Admin memeriksa pengembalian
Mobile: Peminjaman (admin) → tab Pengembalian → chip Pengajuan → /asset_admin_detail_pengembalian → GET /asset/pengembalian-karyawan/CMP001202610002. Detail memuat lokasi_gudang[] (gudang milik company) untuk dropdown Lokasi Tujuan Asset. Admin memilih Rusak pada proyektor, mengetik "Lensa tergores", memilih Gudang Servis, lalu Konfirmasi (langsung dikirim, tanpa sheet).
Dikirim
POST /asset/pengembalian-karyawan/CMP001202610002/konfirmasi
{
"items": [
{ "id_item": "CMP001202610003", "kondisi": "rusak",
"id_asset_lokasi_m": "CMP001202610001", "keterangan_kerusakan": "Lensa tergores" }
]
}
Diproses API (AssetAdminPengembalian_c → konfirmasiPengembalianAdmin)
1. Validasi: kondisi ∈ normal/rusak, keterangan_kerusakan wajib bila rusak, id_asset_lokasi_m wajib. Cek admin, lockCompany.
2. Header pengembalian harus pengajuan; peminjaman asal harus ada dan aktif.
3. Semua item wajib diisi kondisi dan lokasi (resolveItemLocations: lokasi harus bertipe gudang, milik company yang sama, tepat satu kali per item).
4. Asset harus masih dipinjam oleh karyawan pemilik transaksi.
5. Per item: UPDATE item (status_item: normal → disetujui, rusak → ditolak; ket_tolak = keterangan kerusakan; id_asset_lokasi_m_tujuan), UPDATE asset_data, siapkan baris log.
6. INSERT asset_trx_log (jenis_log = pengembalian_karyawan).
7. UPDATE header pengembalian → selesai (user_approval, tgl_approval).
8. Bila tidak ada asset pinjaman yang masih dipegang karyawan, peminjaman asal ikut → selesai (di sini A10 tidak pernah dipegang, jadi selesai).
9. Notifikasi pengembalian.dikonfirmasi ke peminjam.
Hasil database
| Tabel | Perubahan |
|---|---|
item …003 |
status_item NULL → ditolak (kode untuk "rusak"); ket_tolak = Lensa tergores; id_asset_lokasi_m_tujuan = CMP001202610001 |
| asset_data A9 | status_asset dipinjam → rusak; pemegang_asset karyawan → gudang; id_pemegang_asset KRY0003 → ''; id_asset_lokasi_m NULL → CMP001202610001; ket_rusak = Lensa tergores; user_update = ADM-001 - Siti Admin |
header pengembalian …002 |
status pengajuan → selesai; user_approval = ADM-001 - Siti Admin; tgl_approval = 2026-10-28 16:30:00 |
header peminjaman …001 |
status aktif → selesai; tgl_update/user_update diisi admin |
asset_trx_log (1 baris baru)
| Kolom | Nilai |
|---|---|
| id_asset_trx_log | CMP00120261000002 |
| id_asset_data | CMP00120260100009 |
| jenis_log | pengembalian_karyawan |
| id_transaksi / nama_tabel_transaksi | CMP001202610002 / asset_trx_peminjaman_karyawan |
| kondisi_awal / kondisi_akhir | normal / rusak |
| posisi_awal / posisi_akhir | karyawan / gudang |
| pemegang_asset / id_pemegang / nama_pemegang | gudang / '' / '' |
| user_input | ADM-001 - Siti Admin |
notif_data: CMP00120261000008 ke KRY0003, id_ref CMP001202610002, rute asset_detail_pengembalian, "Pengembalian asset KRY05-202610002 telah dikonfirmasi admin."
Diterima mobile (HTTP 200)
{ "code": "UPDATED", "message": "Pengembalian asset berhasil dikonfirmasi.",
"data": { "id": "CMP001202610002", "no_trx": "KRY05-202610002", "jenis_status": "pengembalian",
"id_peminjaman_asal": "CMP001202610001", "status_approval": "selesai", "status_label": "Selesai",
"tab": "selesai", "jumlah_normal": 0, "jumlah_rusak": 1 } }
Peminjaman asal berstatus selesai sehingga tidak tampil lagi di tab Permintaan Peminjaman maupun Sedang Dipinjam; peminjaman itu kini dapat ditelusuri di tab Riwayat Peminjaman (lihat variasi di bawah).
Kondisi akhir data
asset_trx_peminjaman_karyawan
| id | no_trx | jenis_status | id_peminjaman_asal | status | user_approval |
|---|---|---|---|---|---|
| CMP001202610001 | KRY05-202610001 | peminjaman | NULL | selesai | ADM-001 - Siti Admin |
| CMP001202610002 | KRY05-202610002 | pengembalian | CMP001202610001 | selesai | ADM-001 - Siti Admin |
asset_trx_peminjaman_karyawan_item
| id | header | asset | status_item | ket_tolak | id_asset_lokasi_m_tujuan |
|---|---|---|---|---|---|
| CMP001202610001 | …001 | A9 Proyektor | disetujui | '' |
'' |
| CMP001202610002 | …001 | A10 Layar | ditolak | Sedang dijadwalkan servis | '' |
| CMP001202610003 | …002 | A9 Proyektor | ditolak (= rusak) | Lensa tergores | CMP001202610001 |
asset_data: A9 → rusak @gudang (Gudang Servis), ket_rusak = Lensa tergores; A10 → tersedia @gudang (tidak pernah berpindah). asset_trx_log: 2 baris. notif_data: 8 baris. A9 yang rusak kini menjadi kandidat modul Perbaikan, Garansi, atau Disposal.
Perhatikan bahwa pada item pengembalian, status_item dipakai ulang untuk kondisi (disetujui = normal, ditolak = rusak); API menerjemahkannya menjadi normal/rusak saat dibaca.
Variasi
Semua asset ditolak. Pada langkah 2, bila kedua item ditolak: tanggal dan jam serah terima tidak diminta, header status = ditolak, ket_tolak = "Seluruh asset peminjaman ditolak.", jadwal tetap 0000-00-00/00:00:00, kedua asset kembali tersedia, notifikasi peminjaman.ditolak ke peminjam, message "Peminjaman asset berhasil ditolak.".
Karyawan membatalkan setelah dikonfirmasi. Pada langkah 3, tombol Batalkan → POST /asset/karyawan/peminjaman/{id}/batalkan (tanpa body). Syarat status pengajuan atau dikonfirmasi. Asset yang disetujui (A9) kembali tersedia/gudang, header status = dibatalkan (v1.6; sebelumnya ditolak), ket_tolak = "Dibatalkan oleh karyawan.", notifikasi peminjaman.dibatalkan ke admin, message "Peminjaman asset berhasil dibatalkan.". Tidak ada baris log.
Karyawan membatalkan saat pengajuan (v1.6). Andi menyadari salah memilih asset segera setelah langkah 1, sebelum admin memutuskan. Pada detail transaksi yang masih Menunggu Konfirmasi hanya ada tombol Batalkan (dialog "Peminjaman yang dibatalkan tidak dapat dipulihkan.").
POST /asset/karyawan/peminjaman/CMP001202610001/batalkan
Diproses batalkanKaryawan: header pengajuan diterima; karena status pengajuan, semua item (status_item NULL: A9 dan A10) diambil; kedua asset yang masih proses + gudang diubah menjadi tersedia, gudang, id_pemegang_asset = ''; header menjadi dibatalkan, ket_tolak = "Dibatalkan oleh karyawan.", tgl_update/user_update = EMP-003 - Andi Contoh. Tidak ada baris asset_trx_log. Notifikasi peminjaman.dibatalkan dikirim ke kedua admin ("Andi Contoh membatalkan peminjaman asset KRY05-202610001."). Peminjaman tampil di tab Permintaan Peminjaman → chip Dibatalkan.
{ "code": "UPDATED", "message": "Peminjaman asset berhasil dibatalkan.",
"data": { "id": "CMP001202610001", "no_trx": "KRY05-202610001", "status_approval": "dibatalkan",
"status_label": "Dibatalkan", "tab": "dibatalkan" } }
Mencoba membatalkan lagi, atau membatalkan peminjaman yang sudah aktif, ditolak dengan HTTP 422 "Peminjaman asset sudah tidak dapat dibatalkan.". Bila admin lebih dulu mengonfirmasi, Andi tetap bisa membatalkan (status dikonfirmasi); bila Andi lebih dulu membatalkan, konfirmasi admin ditolak 422 "Peminjaman asset sudah tidak menunggu konfirmasi.".
Tab Riwayat dengan pengembalian (v1.6). Setelah langkah 5, peminjaman CMP001202610001 berstatus selesai dan muncul di tab Riwayat Peminjaman sebagai satu entri bersama pengembaliannya.
GET /asset/karyawan/peminjaman?tab=riwayat&limit=20&offset=0
{ "code": "SUCCESS", "message": "Data peminjaman asset berhasil ditemukan.",
"data": {
"filters": [
{"value": "pengajuan", "label": "Pengajuan"},
{"value": "dikonfirmasi", "label": "Dikonfirmasi"},
{"value": "ditolak", "label": "Ditolak"},
{"value": "aktif", "label": "Aktif"},
{"value": "dibatalkan", "label": "Dibatalkan"}
],
"records": [
{ "id": "CMP001202610001", "no_trx": "KRY05-202610001", "tgl_pengajuan": "2026-10-22",
"keperluan": "dinas", "tujuan_peminjaman": "Presentasi klien di Surabaya",
"tgl_pinjam": "2026-10-24", "tgl_kembali": "2026-10-28",
"jenis_status": "peminjaman", "status_approval": "selesai", "status_label": "Selesai", "tab": "selesai",
"jumlah_asset": 2, "tgl_diterima": "", "terlambat": false,
"pengembalian": [
{ "id": "CMP001202610002", "no_trx": "KRY05-202610002", "tgl_selesai": "2026-10-28",
"jumlah_normal": 0, "jumlah_rusak": 1 }
] }
],
"pagination": { "limit": 20, "offset": 0, "next_offset": 1, "has_more": false } } }
Daftar diurut tgl_update desc. jumlah_asset = 2 karena header menghitung semua item (A9 disetujui dan A10 ditolak). Mobile menampilkan kartu: nomor KRY05-202610001, periode 24–28 Okt 2026, "2 Asset", badge Selesai, dan ringkasan "Dikembalikan 28 Okt 2026 · 0 normal · 1 rusak". Membuka kartu memanggil detail (API-010) yang memuat pengembalian[] lengkap (status Selesai, aset A9 kondisi = rusak, keterangan_kerusakan = Lensa tergores, lokasi_tujuan = Gudang Servis). Admin melihat entri yang sama lewat GET /asset/peminjaman-karyawan?tab=riwayat (semua karyawan) dan detailnya read-only.
Pengembalian normal. Pada langkah 5, bila kondisi = normal: item disetujui, A9 → tersedia @gudang pilihan admin, ket_rusak = '', log kondisi_akhir = normal, jumlah_normal = 1.
Terlambat. Peminjaman aktif setelah tgl_dikembalikan tetap aktif di database; API menghitung terlambat = true dan mobile menampilkan label Terlambat serta peringatan pengembalian.
Kasus gagal yang umum
| Situasi | Langkah | HTTP | Pesan | Perubahan database |
|---|---|---|---|---|
| Tanggal pinjam sebelum hari ini | 1 | 422 | Tanggal pinjam tidak boleh sebelum hari ini. | Tidak ada |
| Tanggal kembali sebelum tanggal pinjam | 1 | 422 | Tanggal kembali tidak boleh sebelum tanggal pinjam. | Tidak ada |
Asset bukan tersedia + gudang |
1 | 422 | Satu atau beberapa asset tidak tersedia untuk dipinjam. | Tidak ada |
| Asset ada di onboarding aktif | 1 | 422 | Satu atau beberapa asset sedang diproses pada onboarding. | Tidak ada |
| Asset ada di peminjaman lain | 1 | 422 | Satu atau beberapa asset sedang diproses pada peminjaman lain. | Tidak ada |
| Ada item belum diputuskan | 2 | 422 | Seluruh asset peminjaman wajib dikonfirmasi. | Tidak ada |
| Tolak tanpa alasan | 2 | 422 | Alasan penolakan asset wajib diisi. | Tidak ada |
| Ada yang disetujui tanpa jadwal | 2 | 422 | Tanggal serah terima wajib diisi. / Jam serah terima wajib diisi. | Tidak ada |
| Tanggal serah terima di luar periode pinjam | 2 | 422 | Tanggal serah terima harus berada dalam periode peminjaman. | Tidak ada |
| Konfirmasi dua kali | 2 | 422 | Peminjaman asset sudah tidak menunggu konfirmasi. | Tidak ada |
| Pengembalian milik offboarding dikonfirmasi lewat menu ini | 5 | 422 | Pengembalian ini diproses melalui offboarding. | Tidak ada |
| Terima sebelum dikonfirmasi admin | 3 | 422 | Peminjaman asset belum dapat diterima. | Tidak ada |
| Batalkan setelah aktif / selesai / ditolak / dibatalkan | 3 | 422 | Peminjaman asset sudah tidak dapat dibatalkan. | Tidak ada |
| Kembalikan peminjaman yang tidak aktif | 4 | 422 | Hanya peminjaman aktif yang dapat dikembalikan. | Tidak ada |
| Sudah ada pengembalian berjalan | 4 | 422 | Pengembalian asset sedang menunggu konfirmasi. | Tidak ada |
| Asset sedang di offboarding | 4 | 422 | Asset sedang diproses pada offboarding. | Tidak ada |
| Kondisi atau lokasi gudang tidak lengkap | 5 | 422 | Seluruh kondisi asset pengembalian wajib diisi. / Lokasi gudang tujuan setiap asset wajib dipilih. | Tidak ada |
| Lokasi bukan gudang atau company lain | 5 | 422 | Lokasi gudang tujuan tidak valid. / …tidak sesuai. | Tidak ada |
| Rusak tanpa keterangan | 5 | 422 | Keterangan kerusakan asset wajib diisi. | Tidak ada |
| Pengguna bukan admin asset | 2, 5 | 403 | Pengguna tidak memiliki akses admin asset. | Tidak ada |
| Transaksi milik orang lain | 3, 4 | 422 / 404 | Peminjaman asset tidak ditemukan. | Tidak ada |
| Token kedaluwarsa | semua | 401 | Token telah expired. Mobile me-refresh token sekali lalu mengulang request. | Tidak ada |
Semua pelanggaran aturan dilempar sebagai InvalidArgumentException di dalam DB::transaction, sehingga seluruh perubahan request itu dibatalkan. Pesan tersedia dalam bahasa Indonesia dan Inggris mengikuti header locale.
Ringkasan siapa mengerjakan apa
| Tahap | Mobile | API | Database |
|---|---|---|---|
| Ajukan | Pilih asset, keperluan, tujuan, rentang tanggal; AssetPeminjamanRequest |
Validasi, lock, cek asset, ID, insert, reservasi | header, item, asset_data → proses, notif_data |
| Konfirmasi admin | Setujui/tolak per asset, sheet jadwal | Validasi keputusan dan jadwal dalam periode, lepas asset ditolak | item, header, asset_data (ditolak → tersedia), notif_data |
| Terima / batalkan | Dialog konfirmasi | Cek status (batal: pengajuan atau dikonfirmasi) dan asset, serah terima atau pelepasan | header (aktif / dibatalkan), asset_data, asset_trx_log (terima saja), notif_data |
| Riwayat | Tab Riwayat Peminjaman, kartu ringkasan pengembalian | tab=riwayat + attachRiwayatPengembalian / pengembalian[] di detail |
SELECT saja |
| Ajukan pengembalian | Tombol Kembalikan Asset | Cek asset masih dipinjam dan tidak di offboarding, salin item | header + item baru, notif_data |
| Konfirmasi pengembalian | Kondisi + gudang per asset | Validasi lokasi, update asset, tutup peminjaman asal | item, asset_data, asset_trx_log, header (2), notif_data |
| Tampilan | JSON → model → widget; stepper dan timeline | peminjamanStatusMetadata, terlambat, dapat_dikembalikan |
SELECT saja |
46Ringkasan Database Impact
| Event | INSERT | UPDATE | DELETE |
|---|---|---|---|
| Ajukan peminjaman | header, item, notif | asset_data → proses | – |
| Konfirmasi admin | notif | item (keputusan), header (dikonfirmasi/ditolak, jadwal), asset_data (ditolak → tersedia) | – |
| Terima Asset | asset_trx_log, notif | asset_data → dipinjam @karyawan, header → aktif | – |
| Batalkan (pengajuan atau dikonfirmasi) | notif | asset_data → tersedia (pengajuan: semua item; dikonfirmasi: item disetujui; hanya proses + gudang), header → dibatalkan |
– |
| Ajukan pengembalian | header pengembalian, item, notif | – | – |
| Konfirmasi pengembalian | asset_trx_log, notif | item (kondisi, lokasi tujuan), asset_data → tersedia/rusak @gudang + lokasi, header pengembalian → selesai, peminjaman asal → selesai | – |
47Checklist Implementasi
Database
- Header (peminjaman & pengembalian) & item
- Relasi
id_peminjaman_asal - Index list admin & karyawan
- Enum status
- Nilai enum
dibatalkandi DBML dan SQL manualsql-peminjaman-status-dibatalkan.sql(eksekusi di server: ⚠️ belum diverifikasi, prasyarat deploy) - Rapikan data lama "Dibatalkan oleh karyawan." dari
ditolakkedibatalkan(SQL langkah 3, opsional) - Log serah terima & pengembalian
- Migration CREATE TABLE baseline
- Kolom tanggal diterima & kondisi terpisah
Backend
- Ajukan, konfirmasi per asset + jadwal, terima, batalkan
- Ajukan & konfirmasi pengembalian
- Jatuh tempo & terlambat (tampilan)
- Integrasi offboarding
- Batal pengajuan oleh karyawan (v1.6)
- Filter
kondisidan tabdibatalkan/riwayat;pengembalian[]di list riwayat dan detail - Batal/kedaluwarsa oleh admin
- Tolak pengembalian
- Scheduler pengingat jatuh tempo
- Perpanjangan & pengembalian sebagian
Mobile
- List peminjaman/pengembalian (karyawan & admin)
- Form pengajuan dengan picker asset & rentang tanggal
- Detail + Terima/Batalkan/Kembalikan
- Konfirmasi admin + sheet jadwal
- Konfirmasi kondisi pengembalian
- Lokasi gudang tujuan per asset saat pengembalian; lokasi dikosongkan saat asset keluar gudang (v1.1)
- Backfill lokasi asset yang sudah di gudang
- Banner & kartu di Asset Saya
- Halaman 4 tab (Permintaan, Sedang Dipinjam, Pengembalian, Riwayat) dengan chip kondisi dan kartu Riwayat (v1.6)
- Batalkan pada detail status pengajuan; admin read-only pada peminjaman selesai
maxLengthtujuan peminjaman
Documentation
- Business Flow, DB, API, Mobile, Mapping, Manual Book, Testing
- Disinkronkan dengan kode 2026-10-27 (v1.6)
- Screenshot
48Changelog
| Version | Date | Changes |
|---|---|---|
| 1.6 | 2026-10-27 | (A) Status baru dibatalkan (enum status_peminjaman; SQL manual sql-peminjaman-status-dibatalkan.sql, prasyarat deploy): karyawan dapat membatalkan saat pengajuan dan dikonfirmasi, hasil dibatalkan (bukan ditolak), semua/asset disetujui dilepas, tanpa asset_trx_log; BR-012, §14, §15, §21.4, API-012, notifikasi; RISK-002 ditutup, INC-03 diperbaiki. (B) Halaman 4 tab (Permintaan Peminjaman, Sedang Dipinjam, Pengembalian, Riwayat Peminjaman) untuk karyawan dan admin; AssetPeminjamanPageMode = {peminjaman, sedangDipinjam, pengembalian, riwayat}; pemetaan pemanggil lama; §25–§30, §37. (C) API list: parameter kondisi (semua|jatuh_tempo|terlambat, hanya tab=aktif, difilter sebelum paginasi), BR-019, INC-10. (D) Tab Riwayat = peminjaman selesai + pengembalian[] (list) dan pengembalian[] pada detail; pengembalian offboarding sengaja ikut tampil; kartu Riwayat, bagian Pengembalian, admin read-only pada selesai (BR-017, BR-020, INC-04 diperbaiki). (E) §39/§41: RISK-010–014, INC-10/11. (F) Tes: API tests/Asset 386 lulus (4850 assertion, sqlite); mobile test/asset 466 lulus + 5 gagal lama tidak terkait (hasil eksekusi sesi 2026-10-27, tidak dijalankan ulang saat dokumen ini disusun); TC-41–TC-63; §45.4 menambah skenario batal saat pengajuan dan tab Riwayat. Jumlah rute tetap 15. |
| 1.5 | 2026-10-22 | Menambah §45.4 Walkthrough Lengkap dengan Contoh Data (ajukan, konfirmasi admin per asset, terima, ajukan pengembalian, konfirmasi pengembalian dengan lokasi gudang; variasi tolak semua, batalkan, pengembalian normal, terlambat; kasus gagal). Sinkronisasi kode: nama tabel log yang benar adalah asset_trx_log (id_asset_trx_log). |
| 1.4 | 2026-10-20 | Detail peminjaman (karyawan & admin): kartu Data Karyawan kini menampilkan Jabatan, Cabang, Departemen (label Divisi diganti Departemen). API: karyawan.jabatan ditambahkan di getDetail (leftJoin master_jabatan, tanpa SQL/migrasi); mobile: field jabatan di AssetPeminjamanKaryawan; tes asset_karyawan_jabatan_test.dart. |
| 1.3 | 2026-10-12 | Mobile: stepper alur status (AssetAlurStepper.peminjaman) dan kartu timeline vertikal per aset (AssetPeminjamanTimelineCards) di detail peminjaman/pengembalian karyawan dan admin (§26.3, 26.5, 26.6). Teks langkah di grup JSON alur. Tidak ada perubahan API. |
| 1.2 | 2026-10-12 | Sinkronisasi dengan kode 12 Okt 2026: path file mobile mengikuti struktur folder baru (UI/Modul/Asset/<Fitur>/ berhuruf kapital, model di Repository/Model/Asset/). |
| 1.1 | 2026-10-05 | Konfirmasi pengembalian kini wajib memilih lokasi gudang per asset (items[].id_asset_lokasi_m; kolom asset_trx_peminjaman_karyawan_item.id_asset_lokasi_m_tujuan dan asset_data.id_asset_lokasi_m); lokasi dikosongkan saat karyawan menerima pinjaman. BR-014/015/015a, TC-38..40, mobile dropdown Lokasi Tujuan Asset. Nama controller disesuaikan (AssetAdminPeminjaman_c, AssetAdminPengembalian_c, AssetKaryawanPeminjaman_c) |
| 1.0 | 2026-09-26 | Initial documentation — disusun dari source lokal API (Lumen) & MOBILE (Flutter) |