Bot Jualan dengan Multi-Warehouse
Cara buat bot jualan WhatsApp dengan multi-warehouse. Location-based fulfillment, stok terdistribusi, ongkir optimal. Panduan lengkap!
Multi-warehouse = Faster delivery, lower cost!
Bot dengan multi-warehouse memilih gudang terdekat untuk fulfillment - ongkir lebih murah, pengiriman lebih cepat.
Kenapa Multi-Warehouse?
📦 BENEFITS:
SINGLE WAREHOUSE:
❌ Ongkir mahal ke daerah jauh
❌ Delivery lama
❌ Single point of failure
❌ Stok terpusat
MULTI-WAREHOUSE:
✅ Ongkir optimal (gudang terdekat)
✅ Same-day/next-day possible
✅ Redundancy
✅ Stok terdistribusiWarehouse Setup
📍 CONTOH SETUP:
WAREHOUSE JAKARTA (WH-JKT)
- Cover: Jabodetabek, Jawa Barat
- Capacity: 10,000 SKU
- Priority: 1 (main)
WAREHOUSE SURABAYA (WH-SBY)
- Cover: Jawa Timur, Bali, Kalimantan
- Capacity: 5,000 SKU
- Priority: 2
WAREHOUSE MEDAN (WH-MDN)
- Cover: Sumatera
- Capacity: 3,000 SKU
- Priority: 3
WAREHOUSE MAKASSAR (WH-MKS)
- Cover: Sulawesi, Papua, NTT/NTB
- Capacity: 2,000 SKU
- Priority: 4Template Messages
Location Check:
📍 CEK LOKASI PENGIRIMAN
Hai! Untuk kasih estimasi ongkir
dan waktu kirim terbaik:
Kirim KOTA atau KODE POS kamu!
Contoh:
- Surabaya
- 60234
- Jl. Raya Darmo, Surabaya
Atau share lokasi WhatsApp! 📍Warehouse Assignment:
📦 INFO PENGIRIMAN
Alamat: Surabaya, Jawa Timur
✅ KABAR BAIK!
Pesanan kamu akan dikirim dari:
📍 Gudang Surabaya
ESTIMASI:
━━━━━━━━━━━━━━━━━━━━
- Same Day: Rp 15.000 (order < 12:00)
- Next Day: Rp 12.000
- Regular (2-3 hari): Rp 9.000
━━━━━━━━━━━━━━━━━━━━
💡 Lebih cepat & murah karena
gudang ada di kotamu!
Lanjut order?
Ketik ORDER [PRODUK]Stock Check Multi-WH:
📦 CEK STOK
Produk: [NAMA PRODUK]
KETERSEDIAAN:
━━━━━━━━━━━━━━━━━━━━
📍 Jakarta: ✅ 50 pcs
📍 Surabaya: ✅ 25 pcs
📍 Medan: ✅ 15 pcs
📍 Makassar: ❌ Habis
━━━━━━━━━━━━━━━━━━━━
Lokasi kamu: Surabaya
Dikirim dari: 📍 Surabaya (terdekat)
Stok: ✅ Tersedia
Order sekarang?Optimized Shipping:
🚚 OPSI PENGIRIMAN
Alamat: [ALAMAT CUSTOMER]
Gudang: 📍 Surabaya (22 km)
PILIH KURIR:
━━━━━━━━━━━━━━━━━━━━
1️⃣ Same Day (Grab/Gojek)
💰 Rp 18.000
⏰ Hari ini (3-5 jam)
2️⃣ JNE YES
💰 Rp 15.000
⏰ Besok
3️⃣ SiCepat BEST
💰 Rp 12.000
⏰ 1-2 hari
4️⃣ JNE REG
💰 Rp 9.000
⏰ 2-3 hari
━━━━━━━━━━━━━━━━━━━━
Ketik angka untuk pilih!Split Shipment:
📦 INFO PENGIRIMAN
Order: #ORD-20260217-001
⚠️ Pesanan akan dikirim dari
2 GUDANG berbeda:
PAKET 1 (dari Jakarta):
━━━━━━━━━━━━━━━━━━━━
- Produk A x 2
- Produk B x 1
🚚 JNE REG - Rp 15.000
📅 Estimasi: 2-3 hari
PAKET 2 (dari Surabaya):
━━━━━━━━━━━━━━━━━━━━
- Produk C x 1
🚚 JNE REG - Rp 9.000
📅 Estimasi: 1-2 hari
💰 Total ongkir: Rp 24.000
⚠️ Paket akan datang terpisah!
Masing-masing punya no resi.
OK untuk lanjut?Consolidated Option:
💡 OPSI GABUNG PAKET
Mau hemat ongkir?
Semua produk bisa dikirim dari
Jakarta (stok tersedia):
SPLIT (2 paket):
- Ongkir total: Rp 24.000
- Estimasi: Paket 1 (3hr), Paket 2 (2hr)
GABUNG (1 paket dari Jakarta):
- Ongkir: Rp 15.000
- Hemat: Rp 9.000! 💰
- Estimasi: 2-3 hari
Pilih mana?
1️⃣ Split (lebih cepat, 2 paket)
2️⃣ Gabung (lebih hemat, 1 paket)Implementation
Warehouse Selection:
javascript
const warehouses = [
{
id: 'WH-JKT',
name: 'Jakarta',
location: { lat: -6.2088, lng: 106.8456 },
coverage: ['DKI Jakarta', 'Jawa Barat', 'Banten'],
priority: 1
},
{
id: 'WH-SBY',
name: 'Surabaya',
location: { lat: -7.2575, lng: 112.7521 },
coverage: ['Jawa Timur', 'Bali', 'Kalimantan'],
priority: 2
},
// ... more warehouses
];
async function selectWarehouse(customerLocation, productId) {
// Get warehouses with stock
const warehousesWithStock = await getWarehousesWithStock(productId);
if (warehousesWithStock.length === 0) {
return { success: false, error: 'out_of_stock_all_warehouses' };
}
// Calculate distance to each
const distances = warehousesWithStock.map(wh => ({
...wh,
distance: calculateDistance(customerLocation, wh.location)
}));
// Sort by distance
distances.sort((a, b) => a.distance - b.distance);
return {
success: true,
warehouse: distances[0],
alternatives: distances.slice(1)
};
}
function calculateDistance(loc1, loc2) {
// Haversine formula for distance
const R = 6371; // Earth's radius in km
const dLat = toRad(loc2.lat - loc1.lat);
const dLng = toRad(loc2.lng - loc1.lng);
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(toRad(loc1.lat)) * Math.cos(toRad(loc2.lat)) *
Math.sin(dLng/2) * Math.sin(dLng/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}Multi-WH Stock Check:
javascript
async function checkStockAllWarehouses(productId) {
const stocks = await db.inventory.find({ productId });
return stocks.map(s => ({
warehouseId: s.warehouseId,
warehouseName: getWarehouseName(s.warehouseId),
quantity: s.quantity,
available: s.quantity > 0
}));
}
async function allocateStock(order, preferredWarehouse) {
const allocation = [];
let remainingItems = [...order.items];
// Try to fulfill from preferred warehouse first
const preferredStock = await db.inventory.find({
warehouseId: preferredWarehouse,
productId: { $in: remainingItems.map(i => i.productId) }
});
for (const item of remainingItems) {
const stock = preferredStock.find(s => s.productId === item.productId);
if (stock && stock.quantity >= item.quantity) {
allocation.push({
...item,
warehouseId: preferredWarehouse,
fulfilled: true
});
remainingItems = remainingItems.filter(i => i.productId !== item.productId);
}
}
// Fulfill remaining from other warehouses
if (remainingItems.length > 0) {
for (const item of remainingItems) {
const altWarehouse = await findAlternativeWarehouse(item, order.customerLocation);
if (altWarehouse) {
allocation.push({
...item,
warehouseId: altWarehouse.id,
fulfilled: true
});
} else {
allocation.push({
...item,
fulfilled: false,
error: 'out_of_stock'
});
}
}
}
return {
allocation,
isSplit: new Set(allocation.map(a => a.warehouseId)).size > 1,
warehouses: [...new Set(allocation.map(a => a.warehouseId))]
};
}Shipping Cost Calculation:
javascript
async function calculateShipping(allocation, destination) {
const shipments = groupByWarehouse(allocation);
const shippingOptions = [];
for (const [warehouseId, items] of Object.entries(shipments)) {
const warehouse = await getWarehouse(warehouseId);
const totalWeight = items.reduce((sum, i) => sum + i.weight * i.quantity, 0);
const rates = await getShippingRates(
warehouse.location,
destination,
totalWeight
);
shippingOptions.push({
warehouseId,
warehouseName: warehouse.name,
items,
rates
});
}
return {
isSplit: shippingOptions.length > 1,
shipments: shippingOptions,
totalCost: shippingOptions.reduce((sum, s) =>
sum + Math.min(...s.rates.map(r => r.cost)), 0
)
};
}Best Practices
DO ✅
- Auto-select nearest warehouse
- Show customer the benefit
- Handle split shipment clearly
- Offer consolidation option
- Sync inventory real-time
- Fallback warehouse strategyDON'T ❌
- Always ship from main WH
- Hide warehouse info
- Confusing split shipping
- Force expensive shipping
- Out of sync inventory
- No backup planFAQ
Kapan pakai multi-warehouse?
Saat punya customer di berbagai region dan shipping cost jadi masalah.
Split shipment bagus tidak?
Tergantung. Faster but higher cost. Offer choice to customer.
Bagaimana sync inventory?
Real-time sync critical. Use central inventory system.
Kesimpulan
Multi-warehouse = Better logistics!
| Single Warehouse | Multi-Warehouse |
|---|---|
| High shipping cost | Optimized cost |
| Slow to far areas | Fast everywhere |
| Single point risk | Redundancy |
| Limited reach | National coverage |