Design Document · Approved 1 April 2026

Pipeline & Side Effects

Kapan stok berubah, kapan jurnal dibuat, dan siapa yang trigger.


Prinsip #1: 3 Track Status Independen

Status TIDAK boleh dicampur jadi 1 field. Setiap track punya lifecycle sendiri:

ORDER STATUS (business lifecycle): dibuat → diproses → selesai / dibatalkan FULFILLMENT STATUS (warehouse operation): unfulfilled → picking → packed → shipped → delivered / returned PAYMENT STATUS (money flow): unpaid → settled → refunded

Contoh kombinasi yang valid:

SkenarioOrderFulfillmentPayment
Order baru masukdibuatunfulfilledunpaid
Lagi di-pick di gudangdiprosespickingunpaid
Sudah dikirim, uang belum cairdiprosesshippedunpaid
Sampai ke customer, uang belum cairdiprosesdeliveredunpaid
Uang cair dari marketplacediprosesdeliveredsettled
Semua beresselesaideliveredsettled
Mengantar COD (bayar di tempat)diprosesshippedpaid
Dibatalkan sebelum kirimdibatalkanunfulfilledunpaid
Return setelah kirimdiprosesreturnedunpaid
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 "shipped" / XLSX catch-upPotong 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.
ReturnAPI / XLSX / manualReverse stok + reverse jurnal + klaim kurir
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) │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ └─────────────────┼─────────────────┘ ▼ ┌─────────────────────┐ │ process_ship_event()│ │ │ │ • Potong stok (FEFO)│ │ • HPP calculation │ │ • Create piutang │ │ • Audit trail │ │ • Journal (event dt)│ └─────────────────────┘
Masalah sekarang: Kalau staff gak scan (libur 3 hari), tapi marketplace sudah kirim 300 order → LabaBersih gak tau. Stok utuh, jurnal kosong, laporan SALAH. Dengan design ini, XLSX import atau API sync tetap trigger side effects yang sama.

Prinsip #4: Journal Date = Event Date

❌ SALAH: TikTok shipped 28 Maret Nelly upload XLSX 1 April → Jurnal tanggal 1 April → Laporan Maret: revenue KURANG ✅ BENAR: TikTok shipped 28 Maret Nelly upload XLSX 1 April → Jurnal tanggal 28 Maret → Laporan Maret: revenue BENAR

Semua journal date = shipped_at dari source (platform), BUKAN tanggal proses di LabaBersih.


Prinsip #5: Idempotent

Proses yang sama dipanggil 2x = aman. Skip, bukan error.

SkenarioBehavior
Order sudah shipped → API sync bilang "shipped" lagiSKIP (already processed)
XLSX import order yang sudah adaSKIP (by nomor_pesanan)
Daily journal generate 2x hari samaSKIP (by batch_date + store_id)
Rekon order yang sudah settledREJECT (already reconciled)

Full Pipeline

ORDER MASUK (3 sources) XLSX Import / API Sync / Manual Input │ ▼ ┌─────────────────┐ │ CREATE ORDER │ → reserve stok │ order: dibuat │ │ fulfillment: │ │ unfulfilled │ │ payment: unpaid │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ FULFILLMENT │ (internal: gudang kita) │ picking → packed│ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ SHIP EVENT │ → potong stok + HPP + piutang + audit │ 3 trigger: │ journal date = shipped_at │ • staff scan │ │ • API sync │ fulfillment: shipped │ • XLSX catch-up │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ DELIVERED │ → update status only │ trigger: API │ fulfillment: delivered └────────┬────────┘ │ ▼ ┌─────────────────┐ │ SETTLEMENT │ → Dr.Kas / Cr.Piutang │ trigger: │ payment: settled │ • XLSX rekon │ │ • API settle │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ ORDER SELESAI │ ← auto: delivered + settled │ order: selesai │ └─────────────────┘ BRANCHING: ┌─────────────────┐ │ RETURN / RTS │ → reverse stok + jurnal │ fulfillment: │ klaim kurir kalau hilang │ returned │ └─────────────────┘ ┌─────────────────┐ │ CANCEL │ → release reservation only │ (before ship) │ order: dibatalkan └─────────────────┘

Execution Plan (7 Steps)

StepApaImpact
1Migration: split orders.status → order_status + fulfillment_statusSchema change, data migration
2Shared function: process_ship_event()1 function, 3 trigger sources
3XLSX import auto-process shipped ordersCatch-up tanpa API
4Update all callers (ship_shipment, ship_order, etc)Unified ship path
5Update UI filters (fulfillment tabs, payment filter)UI reflects new model
6Update all 268 testsRegression safety
7Daily journal: use shipped_at date, not process dateCorrect accounting period

Status Mapping: Old → New

Old (1 field)Order StatusFulfillment StatusPayment Status
dibuatdibuatunfulfilledunpaid
dikemasdiprosesunfulfilledunpaid
dikirimdiprosesshippedunpaid
selesaiselesaideliveredsettled
rtsdiprosesreturnedunpaid
dibatalkandibatalkanunfulfilledunpaid