Bab 13 · Developer API
API Gudang (Warehouse)
Endpoint /warehouse: katalog barang, stok per cabang sebagai snapshot, dan riwayat barang masuk/keluar beserta cara merekonsiliasinya.
Domain gudang punya satu keanehan yang perlu dipahami lebih dulu: Lensiro tidak menyimpan angka stok sebagai kolom. Stok selalu dihitung ulang dari seluruh riwayat pergerakan barang. Itu membuat angkanya selalu konsisten dengan riwayat, tapi juga membuat stok tidak bisa disinkronkan secara inkremental seperti data lain.
GET /warehouse/items — katalog barang
curl -H "Authorization: Bearer $KEY" "$BASE/warehouse/items?category_id=1&limit=200"
{
"id": 3321,
"code": "FR3321",
"custom_code": "8991234567890",
"display_code": "8991234567890",
"name": "Frame CONTOH X-01 Black",
"category": { "id": 1, "code": "FR", "name": "Frame" },
"sub_category": { "id": 4, "name": "Full Rim" },
"brand": { "id": 9, "name": "CONTOH" },
"sub_brand": { "id": 21, "name": "X Series" },
"color": { "id": 2, "name": "Black" },
"sub_color": null,
"prescription": { "spherical": "-2.00", "cylinder": "-0.50", "addition": null },
"unit": "pcs",
"sell_price": 1000000,
"expired_date": null,
"memo": null,
"created_at": "2025-03-01T00:00:00.000Z",
"updated_at": "2026-08-01T00:00:00.000Z",
"deleted_at": null
}
| Field | Catatan |
|---|---|
id | Kunci upsert. Satu-satunya pengenal yang tidak pernah berubah |
code | Kode internal Lensiro. Bisa berubah kalau kategori barang diganti |
custom_code | Barcode toko atau kode dari sistem lama; null kalau tidak diisi |
display_code | custom_code bila ada, kalau tidak code — inilah yang tercetak di label barcode |
category sampai sub_color | Klasifikasi barang; masing-masing bisa null |
prescription | Ukuran lensa (spherical, cylinder, addition) sebagai teks; semuanya null untuk barang non-lensa |
unit | "pcs" atau null |
sell_price | Harga jual standar, Rupiah bulat |
expired_date | Tanggal kedaluwarsa untuk barang yang punya, misalnya cairan lensa kontak |
Selalu upsert berdasarkan
id. Kalau Anda memakaicodesebagai kunci, katalog Anda akan pecah begitu ada barang yang pindah kategori. Harga modal atau HPP sengaja tidak diekspos di v1.
Detail satu barang: GET /warehouse/items/{id}.
GET /warehouse/items/{id}/stocks
Stok satu barang di seluruh cabang. Tidak berpaginasi (jumlah barisnya sebanyak cabang) dan dibungkus { "data": [...] }. Bentuk barisnya sama dengan /warehouse/stocks di bawah.
GET /warehouse/stocks — snapshot stok
Stok terkini per pasangan (barang, cabang).
curl -H "Authorization: Bearer $KEY" "$BASE/warehouse/stocks?branch_id=2&limit=500"
{
"item_id": 3321,
"item_code": "FR3321",
"item_name": "Frame CONTOH X-01 Black",
"branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
"on_hand": 4,
"as_of": "2026-08-16T02:00:00.000Z"
}
Yang perlu diketahui:
- Ini snapshot, bukan data yang bisa disinkronkan inkremental. Tidak ada
updated_sincedan tidak adadeleted_at— bukan karena terlewat, tapi karena angka stok tidak punya "waktu terakhir berubah" yang bisa dipercaya. Menyediakanupdated_sincedi sini akan jadi janji palsu. - Cara pakainya: tarik ulang seluruh snapshot secara berkala, lalu timpa tabel stok Anda. Jangan mencoba men-diff atau menambahkan selisih.
as_ofadalah waktu server saat snapshot dihitung. Simpan nilai ini supaya Anda tahu seberapa baru angkanya.- Barang nonaktif, yang sudah dihapus dari katalog, tidak muncul.
- Barang yang tidak punya catatan stok sama sekali di sebuah cabang tidak menghasilkan baris — anggap tidak ada baris sama dengan nol.
on_handbisa negatif kalau ada kesalahan pencatatan di lapangan, misalnya barang terjual sebelum penerimaan dicatat. Jangan asumsikan selalu nol atau lebih.
GET /warehouse/stock-movements — barang masuk & keluar
Ledger pergerakan stok, satu baris per kejadian. Ini cerminan laporan Perubahan Stok, dan sumber angka yang menjelaskan kenapa stok bergerak.
curl -H "Authorization: Bearer $KEY" \
"$BASE/warehouse/stock-movements?occurred_from=2026-08-01T17:00:00Z&item_id=3321"
{
"id": "sale:90101",
"type": "SALE",
"quantity_change": -1,
"item": { "id": 3321, "code": "FR3321", "name": "Frame CONTOH X-01 Black" },
"branch": { "id": 2, "code": "PST", "name": "Cabang Pusat" },
"reference": {
"sale_id": 18234,
"sales_order_number": "SO-PST-1042",
"invoice_number": "INV-PST-987"
},
"memo": null,
"occurred_at": "2026-08-13T02:11:40.000Z"
}
quantity_changebertanda: positif berarti stok bertambah, negatif berarti stok berkurang.idberbentuk"{jenis}:{nomor}", misalnyasale:90101ataupurchase-receive:8812. Bentuknya string, bukan angka — pakai apa adanya sebagai kunci upsert.
Jenis pergerakan
type | Tanda | Arti | Isi reference |
|---|---|---|---|
PURCHASE_RECEIVE | + | Penerimaan barang dari pembelian | purchase_receive_id |
TRANSFER_IN | + | Mutasi masuk dari cabang lain, setelah diterima | stock_mutation_id |
ADJUSTMENT | +/− | Penyesuaian stok opname atau penghapusan | adjustment_job_id, adjustment_type |
SALE | − | Barang terjual | sale_id, sales_order_number, invoice_number |
COMP_ITEM | − | Barang bonus atau komplimen dalam transaksi | sale_id, sales_order_number, invoice_number |
PURCHASE_RETURN | − | Retur barang ke supplier | purchase_return_id |
PURCHASE_SENT | − | Barang dikirim untuk pesanan pembelian | purchase_order_id |
TRANSFER_OUT | − | Mutasi keluar ke cabang lain | stock_mutation_id |
FIT_BUFFER | − | Barang dipesan lewat Lensiro Fit | fit_order_id |
Nilai-nilai itu juga yang diterima parameter type. Untuk ADJUSTMENT, adjustment_type bernilai OPNAME atau WRITE_OFF.
Catatan sinkronisasi
Endpoint ini sengaja tidak punya updated_since, karena pergerakan stok bukan data yang hanya bertambah:
TRANSFER_OUTmuncul saat barang dikirim;TRANSFER_INbaru muncul setelah cabang tujuan mengonfirmasi penerimaan. Jadi selama barang di perjalanan, stok berkurang di cabang asal tanpa bertambah di cabang tujuan — itu perilaku yang benar, bukan bug.- Waktu
occurred_atuntukTRANSFER_INmemakai waktu perubahan terakhir dokumen mutasi, karena waktu penerimaan tidak disimpan terpisah. Nilainya bisa bergeser kalau dokumen mutasi diedit. - Penyesuaian opname bisa diubah setelah dibuat.
Pola yang disarankan: tarik ulang jendela 7 hari terakhir secara berkala, misalnya tiap jam, lalu upsert berdasarkan id. Untuk backfill awal, tarik per rentang bulanan dengan occurred_from dan occurred_to.
Rekonsiliasi
Jumlah quantity_change seluruh pergerakan untuk satu pasangan (barang, cabang) sama dengan on_hand di /warehouse/stocks — keduanya dihitung dari sumber yang sama. Kalau angka Anda tidak cocok, penyebab yang paling mungkin:
- Rentang
occurred_from/occurred_tomemotong sebagian riwayat. Jumlah pergerakan sebagian tidak akan pernah sama dengan stok total. - Paginasi berhenti sebelum
has_morebernilaifalse. - Snapshot ditarik pada waktu berbeda dengan pergerakan; bandingkan
as_ofdenganoccurred_atterakhir.
Untuk angka stok yang dipakai mengambil keputusan,
/warehouse/stocksadalah sumber kebenarannya. Pakai/warehouse/stock-movementsuntuk menjelaskan pergerakan dan menyusun kartu stok, bukan untuk menghitung saldo sendiri.

