← Semua dokumen

Asset Management · Dokumentasi Teknis & Manual Book

Peminjaman Asset Karyawan

Karyawan meminjam asset gudang untuk periode tertentu, Admin menyetujui per asset dan menjadwalkan serah terima, lalu asset dikembalikan dan diperiksa kondisinya. Seluruh isi diturunkan dari source code API (Lumen) dan MOBILE (Flutter).

Kode
KRY05
Modul
Asset Management
Versi
1.6 · 27 Okt 2026
Stack
Lumen 10 · Flutter
API
15 endpoint
KRY05-YYYYMMNNN
Daftar isi

Ringkasan dalam 1 menit

Asset pinjaman berstatus dipinjam dan wajib kembali — berbeda dengan Onboarding/Penambahan yang permanen.

Siapa

Karyawan mengajukan, menerima, dan mengembalikan. Admin Asset memutuskan per asset dan memeriksa kondisi saat kembali.

Periode

Tanggal pinjam ≥ hari ini, tanggal kembali ≥ tanggal pinjam. Jadwal serah terima wajib berada di dalam periode pinjam.

Satu tabel, dua jenis

Pengembalian adalah header baru (jenis_status = pengembalian) yang merujuk id_peminjaman_asal.

Siklus peminjaman

Status transaksi dan posisi asset di setiap langkah.

Menunggu KonfirmasiKaryawan: Ajukan Peminjaman

Asset, keperluan, tujuan, tanggal pinjam–kembali.

asset → proses
DikonfirmasiAdmin: setujui/tolak per asset

Jadwal serah terima ditetapkan. Asset ditolak kembali ke gudang.

ditolak → tersedia
AktifKaryawan: Terima Asset

Asset dipegang karyawan. H-1 muncul pengingat, lewat tanggal = Terlambat.

dipinjam @karyawan · asset_trx_log
SelesaiAdmin: cek kondisi

Karyawan menekan Kembalikan Asset; admin menilai Normal / Rusak. Tampil di tab Riwayat.

tersedia / rusak @gudang · asset_trx_log
DitolakTerjadi bila admin menolak semua asset.DibatalkanTerjadi bila karyawan menekan Batalkan saat Menunggu Konfirmasi atau Dikonfirmasi (sebelum menerima asset). Prasyarat deploy: SQL enum dibatalkan.

Mulai dari sini sesuai peran

Lompat langsung ke bagian yang paling relevan.

Business Analyst / PM

Aturan bisnis, siklus, dan status.

Backend Developer

Logika transaksi, dampak asset, dan kontrak API.

Mobile Developer

Bloc, route, screen, dan mapping field.

QA / Tester

63 skenario uji (TC-01–TC-63), edge case, dan risiko.

Technical Support

Pesan error, audit trail, dan notifikasi.

User / Admin Asset

Langkah meminjam, menerima, dan mengembalikan.

Perlu perhatian

Temuan terpenting dari analisis source. Detail lengkap di §39 dan §41.

  • TINGGIPeminjaman yang sudah disetujui tetapi tidak pernah diterima karyawan mengunci asset tanpa batas waktu; admin tidak punya aksi batal. RISK-001
  • TINGGIUrutan deploy: SQL enum dibatalkan harus dijalankan sebelum API v1.6; tanpa itu pembatalan tersimpan kosong. RISK-010 (RISK-002 pembatalan pengajuan sudah ditutup di v1.6)
  • SEDANGTidak ada pengingat otomatis jatuh tempo (scheduler kosong); hanya banner di aplikasi. RISK-003
  • SEDANGPengembalian tidak bisa ditolak atau dibatalkan. RISK-004
  • SEDANGData pembatalan sebelum v1.6 tetap berstatus ditolak; chip Jatuh Tempo berbeda definisi dengan angka dashboard. INC-03/10
Gambaran & Bisnis

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)
Alur
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_log saat 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

Alur
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

  1. 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).
  2. Tekan + → Tambah Peminjaman Asset → Ajukan Peminjaman.
  3. Selama masih Menunggu Konfirmasi: detail menyediakan Batalkan. Setelah admin mengonfirmasi: banner di Asset Saya / detail → Terima Asset atau Batalkan.
  4. Selama aktif: kartu peminjaman aktif di Asset Saya, pengingat jatuh tempo, label Terlambat bila lewat.
  5. Kembalikan Asset di detail peminjaman aktif → pantau di tab Pengembalian; setelah selesai peminjaman pindah ke tab Riwayat.

11.2 Admin Asset

  1. Asset Karyawan → aksi Peminjaman (juga panel ringkasan "perlu diproses" dan "jatuh tempo").
  2. Tab Permintaan Peminjaman → chip Pengajuan → detail → Setujui/Tolak per asset → Konfirmasi → isi jadwal → Submit.
  3. Tab Pengembalian → chip Pengajuan → detail → Normal/Rusak per asset → Konfirmasi.
  4. 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 |


Database

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).


Backend & API

20Backend Architecture

Alur
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, dan aktif hanya jika asset masih dipegang karyawan. Filter tab: pengajuan, dikonfirmasi, ditolak, aktif, dibatalkan, riwayat (v1.6). Tab tanpa nilai (hanya limit/offset) tidak memfilter status, kecuali selesai tetap disembunyikan.
  • Kondisi (v1.6): applyKondisiAktifFilter hanya dipanggil bila tab=aktif; terlambat = tgl_dikembalikan < hari ini, jatuh_tempo = hari ini sampai besok; diterapkan di query SQL sebelum limit+1/offset.
  • Riwayat (v1.6): peminjamanQuery(includeSelesai) + filter status = selesai, reorder() ke tgl_update desc, id desc; attachRiwayatPengembalian mengambil pengembalian selesai untuk seluruh id di halaman dengan satu query (JOIN item, SUM normal/rusak) dan hanya menempelkan yang id_karyawan-nya sama dengan peminjaman. Pengembalian offboarding tidak dikecualikan.
  • Detail peminjaman (v1.6): key pengembalian dari pengembalianForDetail (semua status, urut tgl_input asc, id_karyawan sama) yang memanggil getDetail(..., includeOffboarding = true) per pengembalian.
  • List pengembalian: mengecualikan pengembalian milik offboarding (tab/list/detail Pengembalian).
  • Detail: menghitung terlambat, tgl_diterima (= tgl_update saat aktif), dapat_dikembalikan (hanya untuk karyawan).
  • Jatuh tempo: aktif, tgl_dikembalikan ≤ besok, asset masih dipegang, belum ada pengembalian berjalan; sisa_hari negatif = 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

HTTP
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."].

JSON
{
  "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).

JSON
{
  "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

HTTP
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:

JSON
{
  "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)

HTTP
POST /asset/peminjaman-karyawan/{id}/konfirmasi
JSON
{
  "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:

Alur
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
JSON
{ "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

HTTP
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

HTTP
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)

HTTP
POST /asset/pengembalian-karyawan/{id}/konfirmasi
JSON
{
  "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:

Alur
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
JSON
{ "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

HTTP
GET /asset/karyawan/peminjaman/options
JSON
{
  "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

HTTP
POST /asset/karyawan/peminjaman
JSON
{
  "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.

JSON
{ "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

HTTP
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.

JSON
{ "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)

HTTP
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.

JSON
{ "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

HTTP
POST /asset/karyawan/peminjaman/{id}/pengembalian

Tanpa body (semua asset disetujui ikut dikembalikan). Database impact: INSERT header pengembalian, INSERT item, INSERT notif_data.

JSON
{ "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."


Mobile

25Mobile Architecture

Alur
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

Validasi, Error & Keamanan

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)

Manual Book

36Manual Book User/Admin

A. Karyawan — Meminjam Asset

  1. Buka Asset Saya → menu Peminjaman → tekan +.
  2. Pilih Asset → Tambah Asset → cari dan pilih asset → Pilih. Ulangi bila perlu.
  3. Di Form Peminjaman: Pilih Keperluan (Kantor/Dinas/Pelatihan/Lainnya), isi Tujuan Peminjaman, pilih Tanggal Pinjam dan Tanggal Kembali.
  4. Tekan Ajukan Peminjaman. Status Menunggu Konfirmasi.
  5. 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

  1. Asset Karyawan → aksi Peminjaman → tab Permintaan Peminjaman → chip Pengajuan → pilih transaksi.
  2. Untuk setiap asset pilih Setujui atau Tolak (isi Alasan Penolakan).
  3. Tekan Konfirmasi → isi Jadwal Serah Terima, Jam Serah Terima (dalam periode pinjam) dan Catatan Tambahan → Submit.

C. Karyawan — Menerima Asset

  1. Buka notifikasi Peminjaman Asset Disetujui atau banner di Asset Saya.
  2. Datang sesuai jadwal serah terima, periksa asset secara fisik.
  3. Tekan Terima Asset → konfirmasi. Status Aktif. - Tidak jadi meminjam: tekan Batalkan (tidak dapat dipulihkan). Status menjadi Dibatalkan, bukan Ditolak.

D. Karyawan — Mengembalikan Asset

  1. Perhatikan pengingat jatuh tempo di Asset Saya; lewat tanggal akan tertulis Terlambat. Tab Sedang Dipinjam dengan chip Jatuh Tempo / Terlambat menyaring peminjaman aktif.
  2. Buka detail peminjaman aktif → Kembalikan Asset → konfirmasi.
  3. 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

  1. Peminjaman → tab Pengembalian → chip Pengajuan → pilih transaksi.
  2. Periksa setiap asset: Normal atau Rusak (isi Keterangan Kerusakan).
  3. Pilih Lokasi Tujuan Asset (gudang) untuk setiap asset; default-nya gudang di cabang karyawan.
  4. Tekan Konfirmasi. Asset normal kembali tersedia, asset rusak berstatus rusak, keduanya di gudang yang dipilih; peminjaman otomatis selesai.

F. Melihat Riwayat Peminjaman (Karyawan & Admin)

  1. Buka tab Riwayat Peminjaman. Daftar berisi peminjaman yang sudah selesai (terbaru di atas); admin melihat seluruh karyawan di company, karyawan hanya miliknya.
  2. Kartu menampilkan nomor, periode, jumlah asset, badge Selesai, dan ringkasan pengembalian. Pengembalian yang berasal dari proses offboarding juga ikut dihitung di sini.
  3. 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 –

QA & Risiko

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.


Referensi Developer

42Dependency

Alur
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_item bergantung jenis baris; getDetail memetakan disetujui/ditolak → normal/rusak untuk pengembalian. Pertahankan pemetaan ini bila mengubah enum.
  • Pengembalian offboarding ditandai lewat asset_trx_offboarding_relasi dan selalu dikecualikan dari list/detail/konfirmasi peminjaman (cek Schema::hasTable sebelum dipakai).
  • List peminjaman menyembunyikan aktif yang 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_hari dihitung today → tgl_dikembalikan (negatif = terlambat); pengingat mulai H-1.
  • Konstanta status memakai PeminjamanStatus enum (konsisten), berbeda dengan modul Request.
  • Status dibatalkan (v1.6): hanya untuk baris jenis_status = peminjaman; deploy SQL enum lebih dulu. Hanya asset proses + gudang yang dilepas, tanpa asset_trx_log.
  • List peminjaman paginasi: limit+1 tanpa COUNT; kondisi hanya untuk tab=aktif; tab riwayat memakai reorder() ke tgl_update desc dan tidak ada di filters statis.
  • Riwayat & detail: pengembalian[] ditempel dari query terpisah (satu query per halaman di list, getDetail per pengembalian di detail). Pengembalian offboarding sengaja ikut (keputusan produk).
  • Mobile: AssetPeminjamanPageMode kini 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

Alur
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

Alur
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

Alur
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

HTTP
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

HTTP
POST /asset/karyawan/peminjaman
JSON
{
  "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)

JSON
{ "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

HTTP
POST /asset/peminjaman-karyawan/CMP001202610001/konfirmasi
JSON
{
  "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)

JSON
{ "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

HTTP
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)

JSON
{ "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

HTTP
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)

JSON
{ "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

HTTP
POST /asset/pengembalian-karyawan/CMP001202610002/konfirmasi
JSON
{
  "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)

JSON
{ "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.").

HTTP
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.

JSON
{ "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.

HTTP
GET /asset/karyawan/peminjaman?tab=riwayat&limit=20&offset=0
JSON
{ "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 dibatalkan di DBML dan SQL manual sql-peminjaman-status-dibatalkan.sql (eksekusi di server: ⚠️ belum diverifikasi, prasyarat deploy)
  • Rapikan data lama "Dibatalkan oleh karyawan." dari ditolak ke dibatalkan (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 kondisi dan tab dibatalkan/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
  • maxLength tujuan 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)