Implemented · 280 tests pass · Updated 1 April 2026

Pipeline & Side Effects

3-track status model, event-driven side effects, RTS management.


Prinsip #1: 3 Track Status Independen

Setiap track jalan paralel. Gak saling tunggu.

ORDER STATUS (business lifecycle): dibuat → diproses → selesai / dibatalkan FULFILLMENT STATUS (warehouse + pengiriman): pending → ready_to_ship → picking → packed → shipped → in_transit │ ┌───────┴────────┐ ▼ ▼ delivered rts_detected │ ┌────────┼────────┐ ▼ ▼ ▼ hold_requested retrying returned │ │ └───┬────┘ ▼ delivered (SAVED!) PAYMENT STATUS (uang): unpaid → settled → refunded

Definisi Fulfillment Status

StatusArtinyaKapan
pendingOrder baru masuk, belum ada resiAPI sync order baru
ready_to_shipAda resi, siap diprosesXLSX import / resi masuk dari platform
pickingSedang dipick di gudangStaff mulai pick
packedSudah dikemas, nunggu kurirStaff selesai kemas
shippedKurir sudah ambilShip event (scan/API/XLSX)
in_transitDalam perjalananAPI tracking update
deliveredSampai ke buyerKurir confirm / API
rts_detectedGagal deliver, terdeteksi RTSAPI notify / XLSX
hold_requestedMinta kurir tahan paketAdmin request hold
retryingKurir coba kirim ulangAdmin record retry
returnedPaket balik ke seller (RUGI)Final — gagal deliver

Contoh Kombinasi Status

SkenarioOrderFulfillmentPayment
Order baru dari API (belum ada resi)dibuatpendingunpaid
Order dari XLSX (ada resi)dibuatready_to_shipunpaid
Sudah dikirim, uang belum cairdiprosesshippedunpaid
Sampai, uang belum cairdiprosesdeliveredunpaid
Uang cair dari marketplacediprosesdeliveredsettled
Semua beresselesaideliveredsettled
Dibatalkan sebelum kirimdibatalkanpendingunpaid
RTS terdeteksidiprosesrts_detectedunpaid
RTS di-hold, retry berhasildiprosesdeliveredunpaid
RTS gagal, paket balikdiprosesreturnedunpaid
Order "selesai" = derived. Bukan di-set manual. Otomatis selesai kalau fulfillment = delivered DAN payment = settled.

Prinsip #2: Side Effects = Reaksi dari Event

EventTriggerSide Effects
Order MasukXLSX / API / manualReserve stok
ShipStaff scan / API / XLSXPotong stok (FEFO) + HPP + piutang + audit
DeliveredKurir confirm / APIUpdate status only. Gak ada side effect keuangan.
SettlementXLSX rekon / API settlementJurnal Dr.Kas / Cr.Piutang. Payment = settled.
RTS DetectedAPI / XLSX / manualCreate RTS ticket. Estimate cost.
RTS ReturnedHold gagal / timeoutReverse stok + reverse jurnal + klaim kurir
RTS SavedHold + retry berhasilCancel RTS ticket. Order → delivered.
CancelInternal / externalRelease reservation only (sebelum ship)

Prinsip #3: 3 Source, 1 Function

Semua trigger source masuk ke function yang SAMA. Gak ada logic terpisah per source.

┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Staff Scan │ │ API Sync │ │ XLSX Import │ │ (real-time) │ │ (background)│ │ (catch-up) │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └─────────────────┼─────────────────┘ ▼ ┌─────────────────────┐ │ ship_order() │ │ │ │ • Potong stok (FEFO)│ │ • HPP calculation │ │ • Create piutang │ │ • Audit trail │ │ • Journal (event dt)│ └─────────────────────┘

Prinsip #4: Journal Date = Event Date

IMPLEMENTED. Semua journal date = order.shipped_at (event date), BUKAN tanggal proses.
TikTok shipped 28 Maret Nelly upload XLSX 1 April → Jurnal tanggal 28 Maret ✅ → Laporan Maret: revenue BENAR

Prinsip #5: Idempotent

SkenarioBehavior
Ship order 2xSKIP (:already_shipped)
XLSX import order yang sudah adaSKIP (by nomor_pesanan)
Detect RTS 2x untuk order samaSKIP (return existing ticket)
Daily journal generate 2x hari samaSKIP (by batch_date + store_id)
Rekon order yang sudah settledREJECT (already reconciled)

RTS Management — CORE FEATURE

Cost per RTS: Rp 80.000 - 125.000+
CAC Rp 60.000 + fulfillment Rp 5.000/pcs + ongkir balik Rp 15.000 + stok rusak (jika).
Scale: 300 order/hari × 5% RTS = 15 RTS/hari = Rp 45.000.000/bulan RUGI.

Flow: Early Intervention

RTS DETECTED (dari API/XLSX) │ Eligible untuk hold? │ ┌────┴────┐ │ YES │ NO ▼ ▼ HOLD REQ. RETURNED 💀 (langsung rugi) │ ▼ RETRYING (kurir coba lagi) │ ┌────┴────┐ │ SUKSES │ GAGAL ▼ ▼ DELIVERED ✅ RETURNED 💀 (SAVED!) (rugi)

Functions (Implemented)

FunctionDeskripsi
detect_rts/1Create ticket + update order. Idempotent.
request_hold/1Minta kurir tahan paket
record_retry/1Record retry (increment count)
resolve_saved/1ORDER SAVED — paket sampai
resolve_returned/1Final returned — RUGI
dashboard_metrics/2Save rate, active count, estimated loss
Value Proposition: Save rate 50% = 7.5 saved/hari × Rp 100.000 = Rp 22.500.000/bulan diselamatkan. Ini alasan seller bayar LabaBersih.

XLSX Import — Smart Routing

1 upload, sistem yang routing berdasarkan status platform:

Status PlatformActionFulfillment Status
Perlu Dikirim / AWAITING_COLLECTION / CREATEImport → ready_to_shipready_to_ship
Sedang Dikirim / IN_TRANSITSkip (status update dari API)
Selesai / DELIVERED / COMPLETEDSkip (status update dari API)
RTS / RETURNEDSkip (status update dari API)
Dibatalkan / CANCELLEDImport → dibatalkanpending
XLSX = import order BARU (siap kirim). Status update (shipped → delivered → RTS) nanti dari API sync. Bukan dari XLSX.

Platform Status Mapping

Setiap platform punya 10-20 status. Kita cuci jadi ~10 status internal yang seragam.

Platform StatusInternal Fulfillment
UNPAID, ON_HOLD, AWAITING_SHIPMENT, Belum Bayarpending
AWAITING_COLLECTION, Perlu Dikirim, CREATE/PICKUPready_to_ship
IN_TRANSIT, Sedang Dikirim, ON_DELIVERYin_transit
DELIVERED, Selesai, COMPLETEDdelivered
RTS, RETURNED, SHIPMENT_RETURNrts_detected
CANCELLED, Dibatalkan(order_status: dibatalkan)

Raw platform status disimpan di marketplace_data untuk referensi.


Full Pipeline

ORDER MASUK (3 sources) XLSX Import / API Sync / Manual Input │ ▼ ┌──────────────────┐ │ CREATE ORDER │ → reserve stok │ order: dibuat │ │ fulfillment: │ │ pending / │ (API: pending, XLSX: ready_to_ship) │ ready_to_ship │ │ payment: unpaid │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ FULFILLMENT │ (internal gudang) │ picking → packed │ └────────┬─────────┘ │ ▼ ┌──────────────────┐ │ SHIP EVENT │ → potong stok + HPP + piutang + audit │ 3 trigger: │ journal date = shipped_at (event date) │ • staff scan │ │ • API sync │ fulfillment: shipped → in_transit │ • XLSX catch-up │ └────────┬─────────┘ │ ┌────┴──────────────────────┐ ▼ ▼ DELIVERED ✅ RTS DETECTED ⚠️ fulfillment: delivered fulfillment: rts_detected │ │ ▼ ┌────┴────┐ SETTLEMENT ▼ ▼ Dr.Kas / Cr.Piutang HOLD REQ. RETURNED 💀 payment: settled │ │ ▼ ▼ RETRYING ORDER SELESAI │ (delivered + settled) ┌───┴───┐ ▼ ▼ DELIVERED RETURNED 💀 (SAVED!) (rugi) CANCEL (before ship): order: dibatalkan → release reservation

Implementation Status

StepApaStatus
1Migration: split status → 3 track (order/fulfillment/payment)✅ Done
2Schema: Order 3-track + RtsTicket✅ Done
3Shared ship function (do_ship_order)✅ Done
4RTS Management: detect → hold → retry → saved/returned✅ Done (13 tests)
5XLSX import smart routing by status✅ Done
6Update all callers + tests✅ Done (280 tests pass)
7UI: fulfillment tabs + RTS ticket actions✅ Done
8Daily journal: shipped_at as event date✅ Done
9Fulfillment status split: pending + ready_to_ship✅ Done