Siklus status Order: Kontrak & Spot Order (OTO)
Order kontrak/agreement dan Spot Order bukan dua sistem yang berbeda. Keduanya memakai Order dan alur pengiriman yang sama persis, mulai dari driver ditugaskan sampai pencairan dana. Karena itu dokumen ini menggabungkan keduanya jadi satu alur, bukan dua dokumen terpisah. Tabel "Bedanya order kontrak vs Spot Order" di bawah merangkum persisnya di mana saja bedanya.
"OTO" di judul dokumen ini adalah nama lama Spot Order yang masih dipakai di kode (field isOneTimeOrder) — bukan fitur atau sistem terpisah. Ini kenapa banyak status Spot Order di bawah diawali prefix oto (otoCreated, otoNegotiation, otoWaitingPayment, dst.).
Sumber lain yang lebih rinci untuk urutan API per langkah (khusus Spot Order, format sequence diagram) ada di diagram sequence Spot Order, dibuat tim backend sendiri.
Definisi
Aktor
| Aktor | Siapa/apa | Aplikasi |
|---|---|---|
| Shipper | Pengirim barang, pemilik order | Web |
| Transporter / Admin | Perusahaan penyedia truk & driver, atau admin internal Karlo yang bertindak atas nama transporter — backend memperlakukan keduanya sama | Web |
| Driver | Pengemudi truk | Mobile |
| PIC Warehouse | Penanggung jawab gudang muat/bongkar | Karlo Web Field |
| Sistem / Karlo Backend | Backend Karlo sendiri, menulis status secara otomatis tanpa ada orang yang menekan tombol | — |
| Cron / Terjadwal | Tugas backend yang jalan sendiri di jam tertentu, bukan dipicu satu kejadian tertentu | — |
| Xendit | Penyedia pembayaran & pencairan dana pihak ketiga | — |
| WA Gateway | Jalur pengiriman WhatsApp, dipakai untuk mengirim kode OTP dan link Karlo Web Field ke PIC | — |
Aplikasi
| Nilai | Apa itu |
|---|---|
| Web | karlo_platform, situs utama — tampilannya beda tergantung yang login Shipper atau Transporter/Admin, tapi satu aplikasi yang sama |
| Mobile | Aplikasi driver. Backend sendiri menyebutnya "kTrip" berulang kali di kode (nama alur pengiriman, field versi aplikasi, dst.), tapi karena kode aplikasi mobilenya tidak ada di repo ini, sebaiknya dikonfirmasi ke tim mobile apakah ini masih nama produk yang dipakai sekarang |
| Karlo Web Field | Bukan aplikasi yang di-install, halaman web yang dibuka PIC Warehouse lewat link "magic login" yang dikirim ke WhatsApp mereka (lihat Fase 6.1) |
Dipicu oleh
Kolom "Dipicu oleh" di tabel-tabel Fase di bawah menandai apa yang membuat sebuah status ditulis:
| Nilai | Artinya |
|---|---|
| API | Ada permintaan langsung dari salah satu aplikasi di atas yang membuat status ini berubah |
| AUTO | Backend menulis status ini sendiri, efek samping dari proses lain, tanpa permintaan terpisah dari siapa pun |
| WEBHOOK | Xendit yang mengirim kabar balik ke Karlo (pembayaran masuk, pencairan selesai, dst.) |
| CRON | Tugas terjadwal yang mengecek dan mengubah status sendiri |
Shipment
flowchart TD
O["Order"] --> S0["shipment ke-0<br/>warehouseFrom → warehouseTo"]
O --> S1["shipment ke-1<br/>warehouseFrom → warehouseTo"]
O -.-> Sn["shipment ke-n<br/>..."]Satu Order bisa membawa lebih dari satu shipment (multi-drop) — tiap shipment adalah satu pasangan titik muat-bongkar (gudang asal dan gudang tujuan) beserta barangnya sendiri. Beberapa hal yang kelihatannya berlaku di level Order (misalnya status POD ditolak/tidak, lihat "Cara mengetahui POD ditolak" di Fase 6) sebenarnya dicek di level shipment ini, bukan Order itu sendiri.
Status history vs status yang berlaku
Order punya dua hal yang beda: status yang berlaku (satu value tunggal yang ditampilkan, mewakili "order sedang di tahap apa sekarang") dan status history (log semua perubahan yang pernah tercatat — bisa berisi lebih banyak entri daripada tahap yang benar-benar pernah terlihat).
Biasanya keduanya selaras: status baru ditulis, status history bertambah, status yang berlaku ikut pindah ke situ. Tapi backend punya daftar status tertentu yang dianggap sekadar penanda, bukan tahap nyata — begitu salah satunya ditulis, backend dalam proses simpan yang sama langsung mengembalikan status yang berlaku ke status nyata sebelumnya. Jadi status itu tetap masuk status history (buktinya kejadian itu memang terjadi), tapi tidak pernah benar-benar terlihat sebagai status yang berlaku, sekalipun sesaat.
Contoh konkretnya, order yang sedang menuju lokasi muat lalu tiba di sana (dengan verifyCargoLoading aktif untuk order itu):
| Urutan | Ditulis ke status history | Status yang berlaku sesudahnya |
|---|---|---|
| 1 | assignDriver | assignDriver |
| 2 | startToLoadingPoint | startToLoadingPoint |
| 3 | arriveAtLoadingPoint (GPS) | tetap startToLoadingPoint — dikembalikan otomatis |
| 4 | verifyCargoLoading | tetap startToLoadingPoint — dikembalikan lagi |
| 5 | finishLoading | finishLoading |
Baris 3 dan 4 nyata terjadi dan tercatat, tapi siapa pun yang mengecek "status order ini sekarang apa" di antara langkah 2 dan 5 akan selalu melihat startToLoadingPoint, bukan arriveAtLoadingPoint atau verifyCargoLoading, walau keduanya baru saja ditulis.
❓ Kenapa mekanisme ini ada sama sekali belum dikonfirmasi ke tim backend — dugaan yang masuk akal (bukan fakta): supaya sistem yang bergantung pada "status order sekarang" tidak bingung oleh kejadian yang bukan tahap baru nyata (ping GPS, toggle pengaturan, checklist internal, dst.), sistem-sistem itu hanya perlu tahu status pengiriman yang nyata, bukan tiap detail kecil yang terjadi.
⚠️ Penting buat siapa pun yang membaca respons API langsung (Postman, atau tampilan apa pun yang menunjukkan status history apa adanya, misalnya linimasa/timeline): hanya statusCode yang direvisi ulang seperti di atas — array statusHistory itu sendiri tidak pernah difilter, endpoint detail order mengembalikan status history itu apa adanya. Jadi kalau melihat baris terakhir status history kelihatan seperti status yang sedang berlaku (karena memang itu entri paling baru yang ditulis), itu bisa membingungkan kalau disangka order benar-benar berhenti di situ — cek statusCode (atau tampilan lain yang membacanya), bukan entri terakhir status history, untuk tahu status yang benar-benar berlaku.
Bentuk respons API-nya kira-kira begini, persis di baris 3 pada tabel contoh di atas (baru saja tiba di lokasi muat) — perhatikan statusCode di level atas masih startToLoadingPoint, padahal entri paling baru di statusHistory sudah arriveAtLoadingPoint:
{
"statusCode": "startToLoadingPoint##1",
"status": "Menuju Lokasi Muat",
"statusHistory": [
{ "statusCode": "assignDriver##1", "status": "Pengemudi Ditugaskan" },
{ "statusCode": "startToLoadingPoint##1", "status": "Menuju Lokasi Muat" },
{ "statusCode": "arriveAtLoadingPoint##1", "status": "Tiba di Lokasi Muat" }
]
}Kalau hanya melihat baris terakhir statusHistory (atau UI yang menampilkannya sebagai "status terbaru"), akan kelihatan seperti order sedang di arriveAtLoadingPoint — tapi statusCode di level atas, yang jadi acuan status sebenarnya, tetap startToLoadingPoint.
Perhatikan juga akhiran ##N di statusCode itu sendiri. Angka itu nomor urut shipment — satu order bisa membawa lebih dari satu shipment (multi-drop), dan nomor ini menandai leg mana yang sedang dikerjakan, dimulai dari 1 untuk shipment pertama, naik setiap pindah ke shipment berikutnya. Status yang tidak terikat ke satu shipment tertentu (done, penanda seluruh order sudah selesai) malah tidak punya angka sama sekali sesudah ##-nya.
Bagian di antara dua tanda pagar itu (nama#X#N) biasanya kosong, makanya kelihatan seperti dua pagar berdempetan (##N) — tapi jalur checklist/tanda tangan digital (checklistCargoLoading/checklistCargoUnloading, Fase 4 & 6) mengisi X dengan id shipment, jadi bentuknya nama#<id shipment>#N, bukan nama##N. Dua bentuk berbeda untuk hal yang sama, angka urut shipment-nya tetap di posisi terakhir di kedua-duanya.
⚠️ Ada satu field lagi yang perlu diwaspadai: statusCodeFilter (versi statusCode tanpa akhiran ##N, dipakai backend secara internal untuk memfilter daftar order). Field ini tidak ikut dikembalikan oleh mekanisme di atas — dihitung dari statusCode sebelum dikembalikan, dan tidak pernah dihitung ulang sesudahnya. Begitu status skip seperti arriveAtLoadingPoint ditulis, statusCodeFilter tetap "nyangkut" di situ selamanya untuk shipment leg itu, walau statusCode sudah benar kembali ke status nyata. Kalau ada tampilan (aplikasi mobile, dashboard) yang membaca statusCodeFilter alih-alih statusCode untuk menentukan status yang ditampilkan, tampilan itu akan terus-menerus salah menunjukkan status skip sebagai status yang sedang berlaku, seolah-olah order berhenti permanen di situ.
Status yang jadi berlaku, tapi nyaris tak tertangkap
Bukan cuma status history-only yang nyaris tidak pernah kelihatan. Status yang memang sempat jadi statusCode (Ya di kolom "Jadi status berlaku?") juga bisa nyaris mustahil tertangkap lewat polling biasa — bedanya cuma soal mekanisme, bukan soal segampang apa tertangkap dari luar. Yang menentukan seberapa lama sebuah status "hidup" adalah apa yang memicu status sesudahnya:
| Pemicu status berikutnya | Berapa lama status ini bertahan | Contoh |
|---|---|---|
| AUTO, ditulis di request yang sama, tanpa jeda sama sekali | Nyaris nol | finishUnloading, langsung ditimpa verifyPodUnloading dalam proses simpan yang sama |
| AUTO, tapi lewat proses singkat terpisah sesudah request selesai | Milidetik sampai beberapa detik | podLoadingVerified/podUnloadingVerified (sekitar 100 milidetik sebelum status berikutnya ditulis), finishLoading (sekitar 6 detik sebelum verifyPodLoading menimpanya) |
| Menunggu aktor luar (approval manusia, webhook pihak ketiga, GPS masuk radius) | Bisa menitan sampai harian, karena memang menunggu kejadian itu | verifyPodUnloading, bertahan sampai Transporter/Admin approve POD |
Ini juga kenapa podUnloadingVerified terasa lebih gampang tertangkap daripada finishLoading/finishUnloading: statusnya ditulis lewat aksi API (approve POD) yang dilakukan orang yang sedang menonton layar itu sendiri, jadi wajar langsung terlihat di respons aksi itu sendiri — bukan karena jendelanya lebih panjang. Jendelanya sendiri (sekitar 100 milidetik) justru lebih pendek dari finishLoading (sekitar 6 detik), dan sama persis panjangnya di kedua sisi (podLoadingVerified maupun podUnloadingVerified).
Konsekuensinya buat siapa pun yang menulis daftar label status tanpa tahu detail ini (tim bisnis menulis wording untuk tim dev implementasikan, misalnya): label untuk status yang dirantai AUTO tanpa jeda, atau lebih parah untuk status yang hanya history dan tidak pernah jadi statusCode sama sekali, kemungkinan besar tidak akan pernah muncul di UI, notifikasi, atau timeline apa pun yang membaca status saat ini — bukan cuma sulit diuji manual, tapi memang secara desain nyaris tidak pernah terlihat ke luar sistem.
Status-status berikut disebut berulang di dokumen ini sebagai "tercatat di status history, tidak pernah jadi status yang berlaku" — lihat Fase masing-masing untuk detail tiap satu, ini hanya daftar rujukannya: arriveAtLoadingPoint/arriveAtUnloadingPoint, outOfLoadingPoint/outOfUnloadingPoint, verifyCargoLoading/cargoLoadingVerified/ cargoLoadingRejected dan versi bongkarnya (Fase 4 & 6), podLoadingRejected/ podUnloadingRejected (Fase 4 & 6), startLoading kalau isUsingOtpBongkar tidak aktif (Fase 4), otoPaymentSuccess (Fase 2), otoCancelInactive (Fase pembatalan), kedua toggle di 6.2, goToLoadingPoint (Fase 4), readyToPlan, update/updateInvoice, dan alihBongkar (semua tiga terakhir di "Status lain yang ada di sistem" di bawah).
Label backend
Kolom "Label backend" di tabel-tabel Fase menunjukkan status/statusAlias — teks yang benar-benar tertulis di kode backend (src/constants/status.js) untuk status itu, bukan paraphrase. Beda dari kolom "Arti" (penjelasan dari dokumen ini, ditulis berdasarkan membaca kode langsung), dan beda juga dari wording bisnis yang belum tentu diimplementasikan di backend — kolom ini murni soal apa yang benar-benar tertulis di kode saat ini.
Satu catatan soal sumbernya: tidak semua status di src/constants/status.js punya statusAlias, sebagian cuma punya status. Untuk status yang begitu, kolom ini cuma satu baris teks, bukan berarti kelupaan menuliskan alias-nya. Beberapa baris di tabel Fase di bawah juga menyebut kalau teks labelnya "dipinjam" dari sisi Spot Order atau sisi order kontrak — itu murni soal dari kode sisi mana teks itu diambil, bukan tanda order kontrak dan Spot Order berjalan beda untuk status tersebut.
Label per-role khusus Spot Order
Kolom "Label backend" di atas berlaku sama untuk order kontrak maupun Spot Order, karena sama-sama membaca status/statusAlias mentah dari src/constants/status.js. Untuk Spot Order saja, backend menimpa teks itu sekali lagi lewat mapSpotOrderStatusByRole (src/utils/spotOrderStatusLabel.js), berdasarkan statusCode dan peran yang sedang mengakses (Shipper atau Transporter). Jadi dua orang yang melihat order Spot Order yang sama persis di status yang sama, bisa membaca teks status yang berbeda di response API-nya masing-masing. Order kontrak tidak lewat fungsi ini sama sekali, teksnya selalu apa adanya dari status.js.
Override ini jalan di tiga tempat: daftar Spot Order shipper, daftar Spot Order transporter, dan detail Spot Order (kedua peran), ditimpakan ke order.status dan ke tiap entri order.statusHistory[].status yang statusCode-nya cocok (setelah dibuang akhiran ##N-nya, lihat Status history vs status yang berlaku). statusCode yang tidak ada di kolom Shipper/Transporter pada tabel di bawah tetap memakai teks asli dari status.js untuk peran itu, tidak ditimpa apa pun.
statusCode | Shipper | Transporter |
|---|---|---|
create | "Draft Order" | (tanpa override) |
otoNegotiation | "Negosiasi" | "Negosiasi" |
otoWaitingPayment | "Menunggu Pembayaran" | "Menunggu Pembayaran" |
otoPaymentInProgress | "Proses Pembayaran" | (tanpa override) |
otoPaymentSuccess | "Pembayaran Berhasil" | (tanpa override) |
requestAssignmentDriver | "Menunggu Penugasan" | "Penugasan Pengemudi" |
assignDriver | "Pengemudi Ditugaskan" | "Pengemudi Ditugaskan" |
startToLoadingPoint | "Menuju Lokasi Muat" | "Menuju Lokasi Muat" |
arriveAtLoadingPoint | "Tiba di Lokasi Muat" | "Tiba di Lokasi Muat" |
startLoading | "Mulai Muat" | "Proses Muat Barang" |
verifyCargoLoading | "Pengecekan Muatan" | "-" |
cargoLoadingVerified | "Muatan Dilaporkan" | "-" |
finishLoading | "Selesai Muat" | "Selesai Muat" |
verifyPodLoading | "Verifikasi Muatan" | "Verifikasi POD dan Muatan" |
podLoadingVerified | "Muatan Terveriifkasi" ⚠️ | "POD dan Muatan Terverifikasi" |
outOfLoadingPoint | "Keluar dari Lokasi Muat" | "-" |
startToUnloadingPoint | "Menuju Lokasi Bongkar" | "Menuju Lokasi Bongkar" |
arriveAtUnloadingPoint | "Tiba di Lokasi Bongkar" | "Tiba di Lokasi Bongkar" |
startUnloading | "Mulai Bongkar" | "Proses Bongkar Muatan" |
finishUnloading | "Pengecekan Bongkar" | "Selesai Bongkar" |
verifyCargoUnloading | "Bongkar Dilaporkan" | "-" |
cargoUnloadingVerified | "Selesai Bongkar" | "-" |
verifyPodUnloading | "Verifikasi Bongkar" | "Verifikasi POD dan Muatan" |
podUnloadingVerified | "Bongkar Terverifikasi" | "POD dan Muatan Terverifikasi" |
done | "Konfirmasi Penerimaan" | "Menunggu Konfirmasi Pengiriman" |
cancel | "Order Dibatalkan" | "Order Dibatalkan" |
orderFinal | "Terkonfimasi Selesai" ⚠️ | "Pengiriman Terkonfirmasi" |
expired | "Order Kadaluarsa" | "Order Kadaluarsa" |
otoDbInProgress | (tanpa override) | "Pencairan Dalam Proses" |
⚠️ Dua isi tabel di atas adalah typo asli di kode, disalin apa adanya, bukan salah ketik dokumen ini: podLoadingVerified sisi Shipper tertulis "Muatan Terveriifkasi" (huruf i dobel salah tempat), dan orderFinal sisi Shipper tertulis "Terkonfimasi Selesai" (kurang huruf "r").
Baris otoDbInProgress perlu satu catatan: override ini baru benar-benar terpakai sejak statusCode pencairan berganti nama pada 3 Agustus 2026 (lihat Fase 8). Sebelum itu key-nya tidak pernah cocok dengan statusCode order mana pun, jadi teks "Pencairan Dalam Proses" tidak pernah muncul. Pasangannya, otoDbSuccess, sampai sekarang tidak punya override sama sekali untuk kedua peran, jadi Transporter membaca teks mentah "Disbursement Success" di fase pencairan sukses. ❓ Belum jelas apakah ini disengaja atau kelewat saat map dibuat.
Nilai "-" untuk verifyCargoLoading, cargoLoadingVerified, outOfLoadingPoint, verifyCargoUnloading, dan cargoUnloadingVerified sisi Transporter memang string dash literal (bukan penanda "tanpa override" seperti sel kosong lainnya), sengaja ditulis begitu di kode karena kelima status ini hanya history, tidak pernah jadi status yang berlaku.
Sebagian statusCode di atas juga menimpa teks statusHistory dengan kalimat yang beda dari override status-nya sendiri, misalnya assignDriver sisi Shipper: status jadi "Pengemudi Ditugaskan" tapi entri statusHistory-nya jadi "Pengemudi Menerima Order". Detail per-baris itu tidak diulang di tabel ini, cek langsung src/utils/spotOrderStatusLabel.js di karlo_be kalau butuh teks statusHistory-nya persis.
Penanda ❓ dan ⚠️
Hampir semua status di dokumen ini sudah ditelusuri langsung ke kode backend, bukan dugaan. Dua penanda dipakai berulang di seluruh dokumen ini, penjelasan lengkapnya ada di Mulai dari sini — ringkasnya: ❓ = belum terverifikasi tuntas, ⚠️ = sudah dikonfirmasi begitu, dan itu sendiri desain yang membingungkan/bermasalah.
❓ Perlu ditanya ke tim mobile, bukan ke backend — hanya berlaku untuk satu kasus (verifyCargoLoading/cargoLoadingVerified, Fase 4): backend punya dua endpoint asli yang sama-sama aktif untuk hal yang sama, dan yang menentukan mana yang terpakai adalah aplikasi driver, bukan sesuatu yang bisa dijawab dari sisi backend saja.
Peta umum
flowchart LR
A["Negosiasi"] --> B["Pembayaran"]
B --> C["Penugasan Driver"]
C --> D["Proses Muat"]
D --> E["Menuju Lokasi Bongkar"]
E --> F["Proses Bongkar"]
F --> G["Menunggu Konfirmasi<br/>Pengiriman"]
G --> H["Selesai / Terkonfirmasi"]
H --> I["Pencairan Dana"]
A -.semua transporter menolak.-> X["Dibatalkan"]
C -.dibatalkan.-> X
D -.dibatalkan.-> X
E -.dibatalkan.-> X
F -.dibatalkan.-> XFase Negosiasi dan Pembayaran hanya berlaku untuk Spot Order. Order kontrak masuk langsung ke Penugasan Driver karena harga dan syaratnya sudah disepakati lewat Agreement sejak awal.
Peta detail: seluruh perjalanan Spot Order
flowchart TD
classDef confirmed fill:#dff7e0,stroke:#1a7a34,color:#14231a
classDef historyOnly fill:#fff3d6,stroke:#a67c00,color:#4d3800
classDef dead fill:#eeeeee,stroke:#999999,color:#666666,stroke-dasharray: 2 2
classDef entry fill:#e6ecff,stroke:#33449a,color:#1a2140
subgraph LEGEND["Legenda"]
direction LR
L1["Dikonfirmasi terjadi<br/>& jadi status berlaku"]:::confirmed
L2["Terjadi & tercatat,<br/>tapi tidak pernah jadi<br/>status yang berlaku"]:::historyOnly
L3["Terbukti tidak<br/>pernah berjalan"]:::dead
L4["Titik masuk order kontrak"]:::entry
end
DR["draft<br/>Belum disubmit"]:::confirmed --> OC["otoCreated<br/>Order diajukan"]:::confirmed --> ON["otoNegotiation<br/>Negosiasi"]:::confirmed
ON -->|disepakati| OWP["otoWaitingPayment<br/>Menunggu Pembayaran"]:::confirmed
ON -.semua tipe truk ditolak.-> CN["cancel<br/>Order Dibatalkan"]:::confirmed
ON -.lewat batas waktu.-> EXP["expired<br/>Order Kadaluarsa"]:::confirmed
OWP --> OPP["otoPaymentInProgress<br/>Proses Pembayaran"]:::confirmed
OPP --> RAD["requestAssignmentDriver<br/>Menunggu Penugasan"]:::confirmed
KONTRAK["Order Kontrak<br/>(Agreement sudah ada,<br/>tanpa negosiasi & pembayaran)"]:::entry --> RAD
RAD --> ASD["assignDriver<br/>Pengemudi Ditugaskan"]:::confirmed
ASD --> STL["startToLoadingPoint<br/>Menuju Lokasi Muat"]:::confirmed
STL -->|GPS otomatis, masuk radius lokasi| AAL["arriveAtLoadingPoint<br/>Tiba di Lokasi Muat"]:::historyOnly
STL -->|hanya kalau OTP Bongkar aktif| SL["startLoading<br/>Mulai Muat"]:::confirmed
STL -->|endpoint finish-loading lama| FL["finishLoading<br/>Selesai Muat"]:::confirmed
AAL --> FL
SL --> FL
STL -->|endpoint finish-loading-verification| VCL["verifyCargoLoading<br/>Pengecekan Muatan"]:::historyOnly
VCL --> CLV["cargoLoadingVerified<br/>Muatan Dilaporkan"]:::historyOnly
CLV --> FL
FL --> VPL["verifyPodLoading<br/>Verifikasi Muatan"]:::confirmed
VPL -->|ditolak| PLR["podLoadingRejected"]:::historyOnly
PLR --> VPL
VPL -->|disetujui| PLV["podLoadingVerified"]:::confirmed
PLV --> STU["startToUnloadingPoint<br/>Menuju Lokasi Bongkar"]:::confirmed
STU -->|GPS otomatis| AAU["arriveAtUnloadingPoint<br/>Tiba di Lokasi Bongkar"]:::historyOnly
STU --> SU["startUnloading<br/>Mulai Bongkar"]:::confirmed
AAU --> SU
SU --> FU["finishUnloading<br/>Selesai Bongkar"]:::confirmed
SU -->|hanya kalau OTP Bongkar aktif, lewat PIC Web Field| VCU["verifyCargoUnloading<br/>Bongkar Dilaporkan"]:::historyOnly
VCU --> CUV["cargoUnloadingVerified<br/>Selesai Bongkar"]:::historyOnly
CUV --> VPU
FU --> VPU["verifyPodUnloading<br/>Verifikasi Bongkar"]:::confirmed
VPU -->|ditolak| PUR["podUnloadingRejected"]:::historyOnly
PUR --> VPU
VPU -->|disetujui| PUV["podUnloadingVerified"]:::confirmed
PUV -->|shipment terakhir| DN["done<br/>Menunggu Konfirmasi Pengiriman"]:::confirmed
DN --> OF["orderFinal<br/>Terkonfirmasi Selesai"]:::confirmed
OF -->|Xendit balas masih diproses| DISB["otoDbInProgress<br/>Pencairan Diproses"]:::confirmed
DISB -->|webhook Xendit selesai| OTU["otoDbSuccess<br/>Pencairan Sukses"]:::confirmed
ASD -.dibatalkan, lihat Fase pembatalan.-> CN
STL -.dibatalkan, lihat Fase pembatalan.-> CN
STU -.dibatalkan, lihat Fase pembatalan.-> CN
FU -.dibatalkan, lihat Fase pembatalan.-> CNcancel bisa dipicu dari lebih banyak titik daripada yang digambar tebal di atas — sengaja cuma diwakili empat garis putus-putus ini (satu per fase besar), bukan dari tiap status, supaya diagram tidak makin padat. Siapa yang boleh membatalkan dari titik mana persisnya beda-beda tergantung peran (Shipper/Transporter/Admin) dan jenis order (kontrak/Spot Order), lihat Fase pembatalan untuk detailnya — diagram ini cuma menunjukkan bahwa pembatalan mungkin terjadi, bukan mendaftar siapa boleh apa.
Order kontrak masuk di kotak biru dan langsung bergabung ke alur yang sama persis dari requestAssignmentDriver sampai selesai. Titik arriveAtLoadingPoint/arriveAtUnloadingPoint sengaja digambar bukan sebagai langkah berurutan biasa, itu ditulis otomatis lewat pelacakan GPS begitu posisi truk masuk radius lokasi, terpisah dari alur langkah-demi-langkah yang lain, lihat Fase 4 untuk detailnya. Status yang terbukti tidak pernah berjalan sama sekali tidak digambar di sini supaya diagram tidak terlalu padat, daftarnya ada di bagian "Status yang terbukti tidak pernah berjalan" di bawah.
Bedanya order kontrak vs Spot Order
| Order kontrak | Spot Order | |
|---|---|---|
| Titik mulai | Langsung dibuat dari Agreement yang sudah disepakati sebelumnya | Diajukan shipper dari nol, belum ada kesepakatan harga/transporter |
| Negosiasi & pembayaran di muka | Tidak ada, harga dan transporter sudah pasti sejak awal | Ada, dua fase penuh sebelum driver ditugaskan |
| Penugasan driver sampai selesai kirim | Sama persis, mesin dan status yang sama | Sama persis |
| Finalisasi & pencairan dana | Sama persis | Sama persis |
| Pembatalan | Hanya menghapus status order, tidak ada pengembalian dana di titik ini | Ada tambahan proses pengembalian dana, karena Spot Order sudah dibayar di muka |
Karena bagian "sama persis" itu adalah mesin yang sama-sama dipakai (bukan dua mesin yang kebetulan mirip), mengubah satu status di tengah alur pengiriman akan memengaruhi order kontrak dan Spot Order sekaligus, kecuali sengaja dibuat bercabang dengan syarat khusus seperti negosiasi/pembayaran di atas, atau seperti fitur OTP Bongkar di Fase 6.1. Perubahan di tengah alur bersama ini bukan hal sepele, dan sebaiknya selalu dikonfirmasi ke backend sebelum dianggap "tinggal ubah sedikit."
Fase 1: Negosiasi (khusus Spot Order)
Shipper mengajukan kebutuhan, sampai 3 transporter diundang, harga ditawar-tawar sebelum apa pun dikunci.
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
draft | Shipper belum submit, order masih tersimpan sebagai draft | "draft" — ⚠️ Spot Order tidak punya representasi khusus untuk status ini di kode, teks ini dipinjam dari sisi order kontrak (dipakai juga oleh order kontrak) | API | POST /spot-order dengan isDraft: true | Shipper | Web | Ya |
otoCreated | Order baru diajukan shipper (submit, bukan draft) | "Offer created, waiting for response" | API | POST /spot-order dengan isDraft: false | Shipper | Web | Ya |
otoNegotiation | Negosiasi aktif. Satu status ini mewakili seluruh proses tawar-menawar, sampai 3 putaran per pihak, tidak ada status terpisah untuk tiap tawaran/penolakan | "In Negotiation" | API | POST /spot-order/:id/negotiate | Shipper | Web | Ya |
Kalau semua transporter menolak semua tipe truk yang ditawarkan, order langsung berpindah ke cancel (lihat Fase Pembatalan). Order juga bisa berakhir di expired kalau melewati batas waktu tanpa ada tindakan (lihat Fase Pembatalan juga), keduanya beda dari "ditolak saat negosiasi". Transisi otoCreated ke otoNegotiation biasanya hanya berjarak milidetik (dua aksi shipper yang berurutan dalam satu alur pengajuan), jadi tidak ada momen nyata bagi siapa pun untuk benar-benar melihat status ini sebagai status yang berdiri sendiri.
Fase 2: Pembayaran (khusus Spot Order)
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
otoWaitingPayment | Penawaran disepakati, transporter terkunci, menunggu shipper mulai bayar | "Offer accepted, waiting for payment" | API | PUT /spot-order/:id/confirm | Shipper | Web | Ya |
otoPaymentInProgress | Shipper sudah mulai transfer, menunggu konfirmasi dari penyedia pembayaran. Status paling lama menggantung di fase ini, bisa sampai batas waktu pembayaran | "Payment in Progress" | WEBHOOK | POST /callback/update | Sistem | — | Ya |
otoPaymentSuccess | Pembayaran berhasil dikonfirmasi | "Payment Done" | WEBHOOK | POST /callback/paid | Sistem | — | Tidak (history) |
Begitu pembayaran sukses, dalam proses yang sama sistem langsung memaksa status berpindah ke requestAssignmentDriver sepersekian detik kemudian.
Fase 3: Penugasan Driver
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
requestAssignmentDriver | Pembayaran sukses (Spot Order) atau order baru dibuat (kontrak), transporter perlu memilih driver | "Request penugasan driver" Request driver assignment | AUTO | dipaksa sistem setelah pembayaran, atau langsung dari pembuatan order kontrak | Sistem | — | Ya |
assignDriver | Driver dan truk sudah dipilih | "Driver dipilih" Driver selected | API | PUT /order/:id/assign-driver | Transporter | Web | Ya |
Fase 4: Proses Muat
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
startToLoadingPoint | Driver menuju lokasi muat — dipicu manual, driver menerima order | "Driver menerima Order" Driver accepts order | API | PUT /order/:id/shipment/start-to-loading-point | Driver | Mobile | Ya |
goToLoadingPoint | Setara startToLoadingPoint, tapi untuk shipment multi-drop yang gudang muatnya sama dengan shipment sebelumnya — order otomatis lanjut tanpa driver perlu "terima order" manual lagi untuk leg itu | "Menuju titik muat" Towards Loading Point | AUTO | pelacakan GPS, lihat catatan di bawah | Sistem | Mobile (GPS latar belakang) | Tidak (history) |
arriveAtLoadingPoint | Driver tiba di lokasi muat | "Sampai di titik muat" Arrive at loading point | AUTO | dipicu otomatis lewat pelacakan GPS truk (posisi masuk radius tertentu di sekitar lokasi muat), lihat catatan di bawah | Sistem | Mobile (GPS latar belakang) | Tidak (history) |
outOfLoadingPoint | Driver sudah meninggalkan lokasi muat | "Keluar dari tempat muat" Exit loading bay | AUTO | sama seperti arriveAtLoadingPoint, pelacakan GPS, posisi keluar radius | Sistem | Mobile (GPS latar belakang) | Tidak (history) |
startLoading | Proses muat dimulai | "Mulai muat" Start loading | API dipanggil driver sendiri, bukan otomatis — ada 3 endpoint yang bisa menulis status ini (lihat catatan di bawah), hanya satu yang punya logika isUsingOtpBongkar secara eksplisit, dua lainnya menyerahkan ke mekanisme history/status-berlaku untuk "melakukan hal yang benar" | PUT /order/:id/shipment/start-loading | Driver | Mobile | Ya, kalau isUsingOtpBongkar aktif — kalau tidak, hanya history (lihat juga Fase 6.1) |
verifyCargoLoading | Driver mengecek kesesuaian muatan sebelum kirim POD | "Verifikasi item muat" Verify cargo loading | API dua endpoint asli sama-sama aktif untuk menyelesaikan proses muat, mana yang terpakai tergantung aplikasi driver, ❓ lihat catatan Definisi | PUT .../finish-loading-verification (endpoint baru, dengan cek muatan) vs PUT .../finish-loading (endpoint lama, tanpa cek muatan) | Driver | Mobile | Tidak (history) |
cargoLoadingVerified | Pengecekan muatan lolos | "Item muat telah diverifikasi" Cargo loading verified | sama seperti verifyCargoLoading | sama seperti verifyCargoLoading | Driver | Mobile | Tidak (history) |
cargoLoadingRejected | Pengecekan muatan tidak lolos, muatan dianggap tidak sesuai | "Item muat tidak sesuai" Cargo loading rejected | sama seperti verifyCargoLoading | sama seperti verifyCargoLoading | Driver | Mobile | Tidak (history) |
checklistCargoLoading | Checklist muatan dikonfirmasi lewat tanda tangan digital — jalur ketiga yang terpisah dari verifyCargoLoading maupun finishLoading langsung, tidak berhubungan dengan OTP Bongkar/PIC Web Field (lihat 6.1) | "Selesai ceklis barang" Completed item checklist | API tidak dibatasi peran tertentu di endpoint-nya. startLoading dan ttdLoading ditulis lebih dulu dalam panggilan yang sama, langsung ditimpa jadi status ini sebelum sempat terlihat sebagai status yang sedang berlaku — beda dari checklistCargoLoading sendiri, yang jadi status berlaku sungguhan dan bertahan sampai driver memanggil finish-loading terpisah untuk lanjut ke finishLoading lewat jalur biasa | PUT .../ttd-loading — payload termasuk signatureInfoLoading (nama, telepon, foto tanda tangan) | Driver / PIC Warehouse | Mobile / Karlo Web Field | Ya |
finishLoading | Proses muat selesai, foto bukti pengiriman (POD) dikirim | "Submit POD & Selesai muat" Submit POD & Completed loading | API | PUT .../finish-loading atau PUT .../finish-loading-verification | Driver | Mobile | Ya, tapi singkat |
verifyPodLoading | POD muat sedang direview | "Pengecekan POD Muat" Load POD Check | AUTO otomatis sekitar 6 detik setelah finishLoading | — | Sistem | — | Ya |
podLoadingRejected | POD muat ditolak Transporter/Admin | "POD Muat Ditolak" Load POD Rejected | API endpoint sama dengan podLoadingVerified, hanya approval:false. Driver lalu kirim ulang lewat endpoint terpisah | PUT /pod/:id/verify {type:'loading', approval:false} (menolak), PUT /upload-pod/:id (driver kirim ulang) | Transporter / Admin | Web | Tidak (history) |
podLoadingVerified | POD muat disetujui | "POD Muat Terverifikasi" Load POD Verified | API | PUT /pod/:id/verify {type:'loading', approval:true} | Transporter / Admin | Web | Ya, tapi ~100 ms |
arriveAtLoadingPoint punya tiga jalur kode berbeda yang bisa menulisnya (sama-sama hanya history). Yang ditelusuri di atas adalah pelacakan GPS otomatis (dikirim lewat POST /tracking/last-location, bisa dari GPS aplikasi mobile driver atau dari alat GPS fisik terpasang di truk). Ada juga dua endpoint manual terpisah untuk hal yang konsepnya sama (PUT .../arrive-at-loading-point dan PUT .../arrive-loading), keduanya wired, belum ditelusuri lebih jauh apakah salah satunya masih benar-benar dipanggil aplikasi sekarang.
startLoading juga punya tiga endpoint berbeda yang bisa menulisnya, semuanya driver-triggered: PUT .../shipment/start-loading (yang ditelusuri di atas, satu-satunya yang punya percabangan isUsingOtpBongkar sendiri secara eksplisit), PUT .../update-status/start-loading (endpoint terpisah untuk integrasi API pihak ketiga), dan PUT .../shipment/ttd-loading (lihat checklistCargoLoading). Dua yang terakhir menulis status ini tanpa syarat, mengandalkan mekanisme history/status-berlaku yang sama untuk "mengembalikannya" kalau isUsingOtpBongkar tidak aktif.
Fase 5: Menuju Lokasi Bongkar
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
startToUnloadingPoint | Driver menuju lokasi tujuan | "Menuju titik bongkar" Towards the unloading point | AUTO langsung setelah POD muat disetujui | — | Sistem | — | Ya |
Fase 6: Proses Bongkar
Bentuknya sama seperti Fase 4, dengan satu perbedaan kata yang disengaja: untuk muat, Transporter dan Shipper sama-sama bilang "Selesai Muat" di langkah selesai-muat. Untuk bongkar, Transporter bilang "Selesai Bongkar" tapi Shipper menyimpan sebutan itu untuk langkah setelahnya (saat muatan terkonfirmasi lolos cek), dan menyebut langkah selesai-bongkar sebagai "Pengecekan Bongkar". Ini bukan salah ketik di lembar kerja bisnis, biarkan seperti itu.
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
arriveAtUnloadingPoint | Driver tiba di lokasi bongkar | "Sampai di titik bongkar" Arrive at the unloading point | AUTO sama seperti arriveAtLoadingPoint di Fase 4 (pelacakan GPS) | — | Sistem | Mobile (GPS latar belakang) | Tidak (history) |
outOfUnloadingPoint | Driver sudah meninggalkan lokasi bongkar | "Keluar dari titik bongkar" Exit unloading point | AUTO sama seperti outOfLoadingPoint di Fase 4 (pelacakan GPS) | — | Sistem | Mobile (GPS latar belakang) | Tidak (history) |
startUnloading | Proses bongkar dimulai | "Mulai bongkar" Start unloading | API | PUT /order/:id/shipment/start-unloading | Driver | Mobile | Ya |
verifyCargoUnloading | Driver mengecek kesesuaian muatan saat bongkar, lewat PIC Warehouse | "Verifikasi item bongkar" Verify cargo unloading | AUTO hanya jalan kalau order.isUsingOtpBongkar aktif, bagian dari fitur OTP Bongkar (lihat Fase 6.1) | — | Driver + PIC Warehouse | Mobile + Karlo Web Field | Tidak (history) |
cargoUnloadingVerified | Pengecekan muatan saat bongkar lolos | "Item bongkar telah diverifikasi" Cargo unloading verified | sama seperti verifyCargoUnloading | sama seperti verifyCargoUnloading | Driver + PIC Warehouse | Mobile + Karlo Web Field | Tidak (history) |
cargoUnloadingRejected | Pengecekan muatan saat bongkar tidak lolos | "Item bongkar tidak sesuai" Cargo unloading rejected | sama seperti verifyCargoUnloading | sama seperti verifyCargoUnloading | Driver + PIC Warehouse | Mobile + Karlo Web Field | Tidak (history) |
checklistCargoUnloading | Checklist muatan dikonfirmasi lewat tanda tangan digital, versi bongkar dari checklistCargoLoading di Fase 4 — jalur terpisah dari verifyCargoUnloading/OTP Bongkar, mekanisme dan ketidakpastiannya sama persis | "Selesai ceklis barang" Completed item checklist | API sama seperti checklistCargoLoading, lihat Fase 4 | PUT .../ttd-unloading — payload sama seperti ttd-loading | Driver / PIC Warehouse | Mobile / Karlo Web Field | Ya |
finishUnloading | Proses bongkar selesai, POD dikirim | "Submit POD & Selesai bongkar" Submit POD & Complete unloading | API | PUT /order/:id/shipment/finish-unloading | Driver | Mobile | Ya, tapi nyaris tanpa jeda |
verifyPodUnloading | POD bongkar sedang direview | "Pengecekan POD Bongkar" Unload POD Check | AUTO langsung, tanpa jeda beberapa detik seperti sisi muat | — | Sistem | — | Ya |
podUnloadingRejected | POD bongkar ditolak Transporter/Admin | "POD Bongkar Ditolak" Rejected Offload POD | API endpoint sama dengan podUnloadingVerified, hanya approval:false. Driver lalu kirim ulang lewat endpoint terpisah | PUT /pod/:id/verify {type:'unloading', approval:false} (menolak), PUT /upload-pod/:id (driver kirim ulang) | Transporter / Admin | Web | Tidak (history) |
podUnloadingVerified | POD bongkar disetujui | "POD Bongkar Terverifikasi" Verified Offload POD | API | PUT /pod/:id/verify {type:'unloading', approval:true} | Transporter / Admin | Web | Ya, tapi ~100 ms |
6.1 Fitur OTP Bongkar & PIC Web Field
Alur verifikasi bongkar yang lebih lengkap dari langkah dasar di tabel atas, aktif untuk order dengan isUsingOtpBongkar: true. Melibatkan aktor ketiga (PIC Warehouse lewat Karlo Web Field) dan kanal terpisah (WA Gateway untuk mengirim OTP). Sejauh yang ditelusuri di kode, flag isUsingOtpBongkar ini hanya pernah dinyalakan lewat alur pembuatan Spot Order — order kontrak tidak lewat kode yang menyalakannya, jadi fitur ini eksklusif untuk Spot Order untuk saat ini, walaupun field-nya sendiri ada di skema Order yang dipakai bersama.
⚠️ Nama flag ini membingungkan untuk satu hal spesifik: dari delapan tempat flag ini dibaca di kode, enam memang soal verifikasi PIC saat bongkar seperti namanya, tapi dua di antaranya (termasuk apakah startLoading jadi status yang berlaku, lihat Fase 4) soal proses muat, bukan bongkar. Riwayat kode menunjukkan gating sisi muat ini ditambahkan seminggu setelah flag ini pertama dibuat (yang aslinya hanya untuk bongkar), memakai flag yang sudah ada alih- alih membuat flag baru — tidak ada komentar atau alasan desain yang lebih dalam yang tercatat di kode, ini sekadar dipakai ulang.
Cara kerjanya: PIC Warehouse dikirim kode OTP dan link Karlo Web Field lewat WhatsApp, driver meminta kode OTP itu ke PIC saat tiba di lokasi bongkar, PIC memeriksa kesesuaian muatan lewat halaman Karlo Web Field (verifyCargoUnloading → cargoUnloadingVerified), baru setelah itu proses bongkar dianggap terverifikasi. Ada juga alur cadangan kalau PIC yang terdaftar tidak bisa dihubungi:
POST .../send-otp-pic-unloading, PUT .../pic-verify-hold-using-code, POST .../change-pic, PUT .../change-pic/approval, GET .../new-pic-status, POST .../report-pic-inactive.
Bukan bagian dari fitur ini, walau namanya terdengar mirip dan sama-sama melibatkan PIC Warehouse: checklistCargoLoading/checklistCargoUnloading (lewat endpoint ttd-loading/ttd-unloading, lihat Fase 4 dan Fase 6) adalah jalur tanda tangan digital yang terpisah total, tidak dicek lewat isUsingOtpBongkar sama sekali. Endpoint scan-code-loading/scan-code-unloading/checklist-loading/approval-loading juga masih ada di kode, tapi statusnya (scanCodeLoading, scanCodeUnloading, approvalLoadingAccepted/Canceled/Rescheduled) sudah terbukti tidak pernah berjalan, lihat bagian "Status yang terbukti tidak pernah berjalan" di bawah.
6.2 Toggle yang ditulis lewat status order
Dua toggle di sisi Transporter/Shipper ini tidak hanya mengubah sebuah pengaturan diam-diam — tiap kali diaktifkan/dinonaktifkan, keduanya menambah satu entri baru ke status history order:
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
updateIsGeofencingActive / updateIsGeofencingNonActive | Pengaturan geofencing diaktifkan/dinonaktifkan | "Setting geofencing telah diubah Aktif" Geofencing settings have been changed to On (versi NonActive setara, kebalikannya) | API toggle manual Transporter — atau AUTO, sistem sendiri menuliskannya sebagai koreksi otomatis saat driver selesai muat/bongkar dengan geofencing yang belum aktif | ⚠️ PUT /order/:id/update dengan body {isFinishWithGeofencing: bool} — bukan endpoint khusus untuk toggle ini, itu endpoint umum untuk mengedit seluruh draft order, hanya kebetulan body sekecil ini saja sudah cukup diterima. Tidak jelas dari nama endpoint-nya kalau ini yang dipakai, sudah dipakai dengan cara yang sama duluan di halaman order kontrak (non-Spot Order) | Transporter / Sistem | Web / — | Tidak (history) |
updateVerifBongkarActive / updateVerifBongkarNonActive | Verifikasi checklist PIC saat bongkar dilewati/diaktifkan kembali (bypass) — berlaku untuk seluruh order, bukan per-shipment | "Verifikasi bongkar telah diaktifkan kembali" Unloading verification reactivated (versi NonActive setara, kebalikannya) | API toggle manual Transporter/Admin | PUT /order/:id/bypass-verifikasi-bongkar {isActive} — endpoint khusus untuk toggle ini | Transporter / Admin | Web | Tidak (history) |
Sama seperti status "hanya history" lain — kedua toggle ini tidak dirancang untuk terlihat sebagai status tersendiri oleh pengguna.
Cara mengetahui POD ditolak atau tidak
Status utama order tidak bisa dipakai untuk mengecek ini — podLoadingRejected/ podUnloadingRejected hanya status history, tidak pernah jadi status yang berlaku. Yang benar-benar dipakai ada di level pengiriman per shipment itu sendiri (bukan level order), ditambah satu penanda di level order yang bernilai benar selama ada penolakan yang belum diselesaikan, dan kembali salah begitu driver mengirim ulang POD-nya.
Fase 7: Finalisasi
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
done | Pengiriman selesai secara fisik. Status paling lama bertahan di seluruh siklus, bisa menggantung sampai 3 hari, menunggu shipper mengonfirmasi barang diterima (atau batas waktu otomatis) | "Order Selesai" Order Completed | AUTO otomatis setelah POD bongkar disetujui pada shipment terakhir | — | Sistem | — | Ya |
orderFinal | Shipper sudah konfirmasi terima (atau batas waktu 3 hari otomatis terlewati) | "Order Finalized" Order Finalized — ⚠️ key-nya di kode adalah otoFinal, bukan orderFinal (lihat "Beberapa nama yang beredar" di bawah) | API atau CRON | konfirmasi manual, atau tugas terjadwal harian yang memeriksa order Spot Order yang sudah lebih dari 3 hari di status done — ⚠️ cron ini hanya menyaring Spot Order, belum ditelusuri apakah order kontrak yang macet di done punya jaring pengaman otomatis yang sama | Shipper / Admin / Sistem terjadwal | Web | Ya |
Secara desain bisnis, konfirmasi ini seharusnya hanya dilakukan shipper, karena barang baru benar-benar "selesai" setelah shipper mengonfirmasi penerimaan (model pembeli-konfirmasi-penerimaan, bukan fitur yang belum dibuat) — Transporter tugasnya berhenti di done. ⚠️ Tapi endpoint konfirmasinya sendiri tidak membatasi ke peran shipper saja, Transporter/Admin/Manager juga secara teknis bisa memanggil endpoint yang sama.
Fase 8: Pencairan Dana
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
otoDbInProgress | Pencairan sedang diproses, Xendit balas "masih diproses" saat endpoint pencairan dipanggil | "Disbursement in Progress" — ⚠️ order kontrak tidak punya status ini sama sekali di kode, teks ini dipinjam dari sisi Spot Order, dipakai juga untuk order kontrak karena memang tidak ada versi lain | API kalau Xendit balas bukan "masih diproses" (gagal atau alasan lain), order dikembalikan ke orderFinal untuk dicoba lagi nanti — bukan lompat ke otoDbSuccess. Transisi ke otoDbSuccess hanya pernah terjadi lewat webhook terpisah, tidak pernah langsung dari panggilan endpoint ini. Endpoint ini juga tidak dibatasi peran tertentu, siapa pun yang sudah login bisa memanggilnya, dan siapa yang benar-benar memanggilnya (tim ops manual, atau proses internal otomatis) belum ditelusuri | POST /disbursement/create {orderId} | Sistem | — | Ya |
otoDbSuccess | Pencairan berhasil, ke transporter | "Disbursement Success" — sama seperti otoDbInProgress, teks ini juga cuma ada di sisi Spot Order | WEBHOOK | POST /callback/disbursement | Sistem | — | Ya |
⚠️ Kedua statusCode di tabel ini baru berganti nama pada 3 Agustus 2026 (karlo_beba0ac030): orderDisbursement jadi otoDbInProgress, dan otoTranspoterUpdate jadi otoDbSuccess. Tidak ada migrasi data yang ikut dijalankan, jadi order yang sudah pernah sampai fase pencairan sebelum tanggal itu tetap menyimpan nama lama di statusCode maupun statusHistory-nya selamanya. Dua konsekuensi nyata: (1) siapa pun yang membaca status order (FE, dashboard, laporan) harus mengenali kedua ejaan, bukan cuma yang baru; (2) query filter backend sekarang mencari nama baru saja, jadi order pencairan lama tidak lagi cocok dengan tab/filter pencairan. ❓ Belum dikonfirmasi ke tim backend apakah data lama memang sengaja dibiarkan atau migrasinya cuma terlewat.
Fase pembatalan
| Status | Arti | Label backend | Dipicu oleh | Endpoint / mekanisme | Aktor | Aplikasi | Jadi status berlaku? |
|---|---|---|---|---|---|---|---|
cancel | Order dibatalkan, oleh salah satu pihak, atau otomatis kalau semua transporter menolak semua tawaran saat negosiasi | "Order Dibatalkan" Order Canceled | API atau AUTO | PUT /order/:id/cancel-order, atau otomatis saat negosiasi gagal total | Shipper / Transporter / Admin / Sistem | Web (kalau dipicu manual), — (kalau otomatis) | Ya |
expired | Order kadaluarsa, batas waktu terlewati tanpa tindakan | "Order sudah kadaluarsa" Order Expired | AUTO | — | Sistem | — | Ya |
otoCancelInactive | ⚠️ Namanya ("Order Cancelled by Inactive Transporter") membingungkan — ini bukan status pembatalan tersendiri, muncul sebagai bagian dari alur cancel/expired di atas, tidak benar-benar dicek terhadap status aktif-tidaknya akun bank transporter | "Order Cancelled by Inactive Transporter" — teks ini cuma ada di sisi Spot Order | AUTO | muncul saat proses refund pembatalan/kadaluarsa dijalankan | Sistem | — | Tidak (history) |
Spot Order dibayar di muka saat penawaran disepakati, jadi pembatalan Spot Order juga memicu pengembalian dana. Order kontrak tidak, cara penagihannya berbeda dan tidak melibatkan pengembalian dana di titik ini. expired berbeda dari kalah negosiasi (yang berakhir di cancel, lihat Fase 1) — ini soal order yang dibiarkan tanpa tindakan sampai lewat batas waktu, bukan ditolak aktif oleh siapa pun.
Status yang terbukti tidak pernah berjalan
Status-status ini tidak punya jalur kode aktif sama sekali, di order kontrak maupun Spot Order manapun. Catatan metodologi, bukan hanya soal dua status ini: backend sebenarnya punya dua urutan langkah pengiriman yang sudah disiapkan sebagai pilihan, satu peninggalan alur lama yang lebih rinci (PIC gudang scan kode kedatangan truk, lalu approve/tolak/jadwalkan ulang, baru checklist barang), satu lagi alur yang sekarang benar-benar dipakai (langsung checklist barang, tanpa scan kode maupun approval terpisah). Sudah ditelusuri sampai ke setiap tempat di kode yang bisa memicu proses muat/bongkar (Web, Mobile, API pihak ketiga) — tidak satu pun yang memilih urutan lama itu. Ini penting diketahui, buat siapa pun, teknis atau tidak: kalau ketemu status-status ini (di tiket lama, desain lama, atau data lama), itu bukan sesuatu yang masih bisa terjadi hari ini di aplikasi manapun, cuma sisa kode dari sebelum alurnya disederhanakan, belum sempat dibersihkan.
Tapi status yang tidak masuk urutan yang aktif belum tentu mati — arriveAtLoadingPoint di Fase 4 adalah contohnya, ditulis lewat pelacakan GPS, sama sekali di luar kedua urutan itu (jalurnya sendiri, terpisah dari urutan langkah manual di atas). Dua baris di bawah ini sudah ditelusuri sampai benar-benar tidak ada jalur lain yang menulisnya, bukan hanya "tidak ada di urutan yang aktif":
| Status | Arti singkat | Kenapa mati |
|---|---|---|
requestOrderToDriver, waitingDriverApprove, noDriverApprove | Alur "sistem otomatis menawarkan order ke semua driver yang available" sebelum transporter memilih manual | Variabel yang menentukan jalur ini dikunci ke nol secara sengaja di kode, sistem selalu langsung ke requestAssignmentDriver — tidak ada endpoint manapun yang menulis status ini |
enterWaitingOnLoading, exitWaitingOnLoading | Driver masuk/keluar antrean di lokasi muat | Baris kode yang menulis status ini sengaja dikomentari (dinonaktifkan) di dalam logika pelacakan GPS, bukan terhapus, tapi juga tidak aktif |
scanCodeLoading, scanCodeUnloading, approvalLoadingAccepted, approvalLoadingCanceled, approvalLoadingRescheduled | Alur lama: PIC gudang scan kode kedatangan truk, lalu menyetujui/membatalkan/menjadwalkan ulang jadwal muat — semacam sistem appointment dermaga sebelum truk boleh mulai muat | ⚠️ Endpoint-nya (scan-code-loading, approval-loading, dan versi bongkarnya) masih ada di kode dan masih bisa dipanggil, tapi selalu membalas error (urutan langkah pengiriman yang backend pakai sekarang tidak pernah menyertakan status-status ini, jadi baris yang menuliskannya tidak pernah tereksekusi) — beda dari dua baris di atas yang endpoint-nya sendiri sudah tidak ada. checklistCargoLoading/checklistCargoUnloading dari alur yang sama tidak ikut mati, keduanya masih hidup lewat endpoint ttd-loading/ttd-unloading yang terpisah, lihat Fase 4 dan Fase 6 |
Status lain yang ada di sistem, di luar alur utama
Status-status ini ada kodenya dan endpoint-nya nyata, tapi belum ditelusuri lebih jauh seberapa sering dipakai di luar dokumen ini:
| Status | Arti singkat | Keterangan |
|---|---|---|
alihBongkar | Reroute satu atau beberapa shipment ke Agreement baru (rute/harga baru) di tengah pengiriman — bukan hanya pindah lokasi bongkar sederhana, lihat catatan di bawah tabel | Disetujui masing-masing pihak (shipper dan transporter) di seluruh rantai order, lihat catatan di bawah |
reuploadPodLoading, reuploadPodUnloading | Kelihatannya untuk menandai "POD diunggah ulang" | Kemungkinan tidak pernah ditulis, proses unggah ulang yang sebenarnya justru mengembalikan status ke verifyPodLoading/verifyPodUnloading, bukan status ini |
finishShipmentUnloading | Kelihatannya penanda "shipment selesai" | Bukan status yang tersimpan di history, hanya proses internal sesaat |
readyToPlan | Penanda "order siap direncanakan rutenya per gudang" — bukan tahap baru dalam urutan status utama (hanya history), lihat catatan di bawah tabel | Endpoint sendiri (PUT /order/:id/ready-to-plan) |
update, updateInvoice | Penanda generik "ada yang diperbarui di order ini" (hanya history) | Bukan bagian dari alur berurutan di atas, tidak menandakan fase tertentu, hanya metadata perubahan |
updateEstimatedTrip | Kelihatannya generik, tapi sebenarnya langkah tersendiri (Transporter mengisi estimasi perjalanan setelah assignDriver) dengan endpoint sendiri | Bukan hanya metadata seperti dua di atas — tapi tidak tercapai lewat alur normal sekarang, karena syaratnya terikat ke requestOrderToDriver yang sudah mati (lihat "Status yang terbukti tidak pernah berjalan") |
requestChooseDriver, approvalChooseDriverAccepted, approvalDriverRejected | Bagian dari fitur 3PL (pihak ketiga memilih driver untuk transporter) | Fitur terpisah dari Spot Order maupun kontrak biasa, di luar cakupan dokumen ini, lihat Peta domain |
readyToPlan ditulis lewat endpoint di atas (beserta labelWarehouse, dipakai buat mengelompokkan order per gudang), biasanya pada order yang sudah di ujung proses bongkar (sekitar finishUnloading/finishShipmentUnloading) — tidak menghalangi atau mengubah alur utama sama sekali, order tetap lanjut normal ke done seperti biasa. Proses "perencanaan rute" yang dimaksud sendiri tidak ada modelnya di karlo_be (bukan model rute jarak antar-gudang yang dipakai untuk kalkulasi peta, itu hal lain yang tidak berhubungan) — kemungkinan diproses di sistem lain di luar cakupan dokumen ini, belum jelas yang mana, lihat Peta domain.
alihBongkar dipicu shipper lewat endpoint update order yang sama dipakai untuk edit order biasa (PUT /order/:id/update dengan isAlihBongkar: true, plus data shipment baru — gudang muat/bongkar, Agreement, rute, harga per shipment). Backend lalu mengumpulkan semua pihak yang perlu setuju (shipper dan transporter di seluruh rantai order, termasuk 3PL kalau ada), pengaju otomatis dianggap sudah setuju, sisanya menunggu. Tiap pihak menyetujui sendiri-sendiri lewat PUT /order/:id/approve-alih-bongkar.
Beda dari readyToPlan, ada flag terpisah (isAlihBongkar) yang benar-benar menahan alur: selama flag ini aktif, driver tidak bisa menyelesaikan proses bongkar (finishUnloading diblokir), jadi order bisa berhenti sesaat di Fase 6 sampai semua pihak setuju.
Beberapa nama yang beredar tidak sama dengan nama di sistem
Kalau mendengar nama-nama ini dalam rapat atau tiket, ini padanannya di sistem: otoFinal yang dimaksud adalah orderFinal — ⚠️ ini bukan hanya sebutan tidak resmi, otoFinal benar-benar key-nya di kode, orderFinal hanya value statusCode-nya.
Beda halnya dengan dua nama pencairan dana. otoDbInProgress dan otoDbSuccess dulu memang cuma sebutan tidak resmi, tapi sejak 3 Agustus 2026 keduanya justru jadi value statusCode yang sebenarnya, menggantikan orderDisbursement dan otoTranspoterUpdate (lihat Fase 8). Jadi kalau ketemu dua nama lama itu hari ini, itu bukan sebutan tidak resmi juga, itu data order lama yang tidak ikut dimigrasi, dan tetap harus dikenali oleh apa pun yang membaca status order.
⚠️ Hati-hati tertukar: otoTranspoterUpdate (typo, tanpa "r", value pencairan dana yang lama) beda sama sekali dari otoTransporterUpdate (ejaan benar, dengan "r") yang disebut di paragraf berikutnya: hanya beda satu huruf, tapi dua hal yang sama sekali tidak berhubungan. Nama yang typo itu sudah tidak ditulis lagi untuk order baru, tapi masih ada di data lama, jadi kebingungan ini belum hilang sepenuhnya.
Lima nama lain terdengar seperti status order yang sesungguhnya tapi sebenarnya bukan, di alur Spot Order (/spot-order/...) yang aktif dipakai sekarang: otoShipperUpdate, otoTransporterUpdate, otoShipperApprove, otoShipperReject, dan otoMaxReject hanya dipakai sebagai label notifikasi di sana, tidak pernah ditulis sebagai status order yang nyata. Order yang gagal karena semua penawaran ditolak (lewat alur ini) tidak berakhir di otoMaxReject, dia berakhir di cancel biasa, sama seperti pembatalan lainnya.
⚠️ Tapi ada satu endpoint lama yang masih terdaftar (/quick-order/:id/update, kelihatannya peninggalan implementasi OTO sebelum ditulis ulang jadi Spot Order) yang justru menuliskan kelima nama ini langsung sebagai status order yang nyata, termasuk otoMaxReject untuk kasus semua penawaran ditolak — bukan cancel. Belum jelas apakah endpoint lama ini masih benar-benar dipakai untuk order yang dibuat hari ini, atau hanya sisa kode untuk order lama dari sebelum penulisan ulang.