Skip to content
ENTERPRISE Enterprise technology · Engineered for your business Enterprise Software · Managed Servers · Business E-mail · LiteSpeed + CloudLinux · Imunify360 Security · cPanel Control · Network profile up to 3 Gbps on eligible plans
AIOR

E-Commerce Inventory Management: Prevent Overselling, Set Safety Stock

Aior

Administrator

Staff member
Joined
Apr 2, 2023
Messages
929
Reaction score
2
Points
18
Age
41
Location
Turkey
Website
aior.com
Thread owner

Stok hatası neye mal olur?​

E-ticarette stok sayısı yalnızca depodaki adet değildir; sitenizde, pazaryerlerinde ve hazırlanmakta olan siparişlerde aynı anda verilen bir sözdür. Stok yanlış yönetildiğinde kayıp üç biçimde ortaya çıkar:
  • Fazla satış (overselling): Elde olmayan ürünü satmak. Sipariş iptal edilir, müşteri bir daha gelmeyebilir; pazaryerlerinde satıcı kaynaklı iptaller satıcı performans ölçütlerine de yansır. Türkiye'de işin hukuki bir boyutu da vardır: Mesafeli Sözleşmeler Yönetmeliği'nin 16. maddesine göre malın stokta bulunmaması, edimin yerine getirilmesinin imkânsızlaşması sayılmaz. Bu yüzden "stokta yok" gerekçesiyle yapılan iptal hukuki risk de taşır.
  • Stoksuz kalma (stockout): Ürünün en çok sattığı anda tükenmesi. Reklam bütçesi boşa gider; ürünün aramada ve pazaryeri listelerinde kazandığı görünürlük zayıflayabilir.
  • Ölü stok (dead stock): Satılmayan ürüne bağlanan sermaye, depo maliyeti ve sonunda indirimle eritme zorunluluğu.
Bu yazı üçünü birlikte yöneten bir yapıyı anlatıyor: doğru stok kavramları, hareket kaydı tabanlı veri modeli, ödeme akışında rezervasyon, çok kanallı senkronizasyon, güvenlik stoğu, yeniden sipariş noktası ve sipariş miktarı hesabı, iadeler, sayım ve izlenecek metrikler.

Dört sayı: eldeki, rezerve, satılabilir, yoldaki​

Tek bir "stok" sütunu sorunların çoğunun kaynağıdır. En azından şu dört kavramı ayırın:
KavramTanımNeyle değişir
Eldeki stok (on hand)Depoda fiziksel olarak duran adetMal kabul, sevkiyat, iade, sayım düzeltmesi
Rezerve (reserved)Siparişi alınmış ama henüz depodan çıkmamış adetSipariş, iptal, rezervasyon süresinin dolması, sevkiyat
Satılabilir stok (available to sell)Eldeki − rezerve − satış tamponuHesaplanır, elle girilmez
Yoldaki stok (incoming)Tedarikçiye sipariş verilmiş, henüz gelmemiş adetSatın alma siparişi, mal kabul
Sitede ve pazaryerlerinde gösterilen sayı eldeki stoktan değil, her zaman satılabilir stoktan türetilir. Eldeki stok depo ve muhasebe içindir; yoldaki stok ise yeniden sipariş hesabında, ön sipariş (pre-order) açarken ve tahmini teslim tarihi gösterirken kullanılır. Satış tamponu ile aşağıda anlatılan güvenlik stoğu farklı şeylerdir: tampon, sayım hatalarına karşı satışa açılmayan birkaç adettir; güvenlik stoğu ise yeniden sipariş hesabının parçasıdır.

Veri modeli: sayaç değil, hareket kaydı​

"Stok = 17" diye tutulan bir sayaç yanlış çıktığında nedenini kimse bulamaz. Bunun yerine her değişikliği bir hareket satırı olarak yazın; eldeki stok, hareketlerin toplamıdır. Örnekler MySQL söz dizimiyle yazılmıştır:
SQL:
CREATE TABLE stock_movements (
  id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  sku          VARCHAR(64) NOT NULL,
  warehouse_id INT NOT NULL,
  qty          INT NOT NULL,          -- giriş artı, çıkış eksi
  reason       VARCHAR(32) NOT NULL,  -- receipt, shipment, return_restock, adjustment, transfer
  ref          VARCHAR(64),           -- sipariş, satın alma ya da sayım numarası
  created_by   VARCHAR(64) NOT NULL,
  created_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_sku_wh (sku, warehouse_id)
);

-- Eldeki stok = hareketlerin toplamı
SELECT sku, warehouse_id, SUM(qty) AS on_hand
  FROM stock_movements
 GROUP BY sku, warehouse_id;
Performans için eldeki ve rezerve adetleri ayrıca bir stock_levels tablosunda, (sku, warehouse_id) üzerinde tekil anahtarla tutabilirsiniz. Ancak bu tablo bir önbellektir: hareketle aynı işlemde (transaction) güncellenir ve gece çalışan bir mutabakat (reconciliation) işiyle doğrulanır. Eldeki adet hareketlerin toplamına, rezerve adet de açık rezervasyonların toplamına eşit olmalıdır; fark çıkarsa alarm üretin, sessizce düzeltmeyin. Varyantlı ürünlerde stok her zaman varyant SKU'su (beden, renk) düzeyinde tutulur; ana ürün yalnızca bir gruplamadır.

Ödeme akışında rezervasyon ve yarış koşulu​

Son ürünü iki müşteri aynı saniyede almaya çalıştığında "önce stoğu oku, sonra düş" yaklaşımı ikisine de satış yapar. Kontrolü ve ayırmayı tek bir koşullu UPDATE sorgusunda birleştirin:
SQL:
-- Yeterli satılabilir stok varsa rezerve et: tek sorgu, yarış koşulu yok
UPDATE stock_levels
   SET reserved = reserved + :qty
 WHERE sku = :sku
   AND warehouse_id = :wh
   AND on_hand - reserved - sell_buffer >= :qty;
-- Etkilenen satır sayısı 1 ise rezervasyon başarılı, 0 ise stok yetersiz
Bunun güvenli olmasının nedeni şudur: iki işlem aynı satırı güncellemeye çalıştığında ikincisi, ilki bitene kadar satır kilidini bekler; koşul ardından satırın güncel değerine göre değerlendirilir. MySQL (InnoDB) ve PostgreSQL, varsayılan yalıtım düzeylerinde (isolation level; sırasıyla REPEATABLE READ ve READ COMMITTED) böyle davranır. PostgreSQL'de REPEATABLE READ ya da SERIALIZABLE kullanıyorsanız ikinci işlem serileştirme hatası alır ve yeniden denenmesi gerekir. Çok kalemli siparişlerde tüm kalemleri tek işlemde rezerve edin, kilitlenmeyi (deadlock) azaltmak için satırları her zaman aynı sırayla (ör. SKU'ya göre) güncelleyin ve kalemlerden biri 0 dönerse işlemi geri alın (rollback).

Rezervasyonun yaşam döngüsü:
  1. Sipariş oluşturulurken: Yukarıdaki sorguyla ayırın ve aynı işlemde bir reservations satırı yazın (durum pending, bitiş zamanı expires_at).
  2. Ödeme onaylanınca: Durumu koşullu bir sorguyla confirmed yapın (WHERE id = :id AND status = 'pending'); süre sınırı kalkar. Etkilenen satır 0 ise rezervasyonun süresi az önce dolmuştur; aşağıda anlatılan geç ödeme yoluna geçin.
  3. Süre dolarsa ya da sipariş iptal edilirse: Durum geçişini yine tek koşullu sorguyla yapın (süre dolumunda status = 'pending', iptalde status IN ('pending', 'confirmed') koşuluyla) ve rezerve adedi yalnızca etkilenen satır 1 ise geri bırakın. Ödeme onayı ile süre dolumu aynı anda çalışırsa yalnızca biri başarılı olur; böylece ödemesi alınmış siparişin stoğu serbest kalmaz. İptal ile süre dolumu çakıştığında da stok iki kez bırakılmaz.
  4. Sevkiyatta: Eldeki stoktan ve rezerve adetten aynı anda düşün, bir shipment hareketi yazın.
Sepete ekleme anında stok ayırmayın; terk edilen sepetler stoğu kilitler. Terk edilen sepetleri ölçmeyi ve geri kazanmayı sepet terk oranı yazımızda ele aldık. Hazır altyapılar da benzer bir mantıkla çalışır: WooCommerce'teki Hold stock (minutes) ayarı, ödenmemiş siparişlerin stoğunu belirtilen dakika boyunca tutar; süre dolunca "Pending payment" (ödeme bekleniyor) durumundaki sipariş iptal edilir ve stok yeniden satışa açılır. Havale gibi çevrimdışı ödeme yöntemleriyle verilen siparişler ise genellikle "On hold" (beklemede) durumuna düşer: bu durumda stok düşülmüş olur ve süre sınırı uygulanmaz, yani ödemesi gelmeyen siparişleri kendi kuralınızla kapatmanız gerekir.

Rezervasyon süresi ödeme yöntemine göre değişir: kartlı ödemede dakikalar yeterliyken havale/EFT siparişlerinde süreyi iş kuralınız belirler. Bu süreyi ve süre dolunca siparişe ne olacağını satış koşullarınızda açıkça yazın. Süresi dolduktan sonra gelen ödemede stok hâlâ varsa yeniden ayırın; yoksa müşteriyi hemen bilgilendirip parayı iade edin ya da müşterinin onayıyla siparişi tarihli bir ön siparişe çevirin.

Varyantlar, setler ve birden fazla depo​

Önceden paketlenip ayrı bir SKU olarak depolanmıyorsa set (bundle) ürünlerin kendi stoğu olmaz; satılabilir set adedi bileşenlerden hesaplanır. Her bileşenin satılabilir adedi setteki adedine bölünür, aşağı yuvarlanır ve bunların en küçüğü alınır. Örnek: bir kamp setinde 1 çadır ve 2 mat varsa, 7 çadır ve 10 mat ile en fazla min(7, 5) = 5 set satılabilir. Set satıldığında hareketler bileşenlere yazılır.

Birden fazla depoda satılabilir stok depo bazında hesaplanır; siparişe hangi deponun atanacağı (en yakın depo, siparişi tek kolide toplayabilen depo, en eski stok) ayrı bir kuraldır. Mağaza stoğunu e-ticarete açıyorsanız (ör. mağazadan teslim, click and collect), mağazadaki sayım doğruluğu çoğunlukla depodakinden düşük olduğu için mağaza stoğuna daha büyük bir satış tamponu koyun.

Çok kanallı satış: site, pazaryerleri ve senkronizasyon​

Aynı ürünü kendi sitenizde, Trendyol, Hepsiburada ya da Amazon'da satıyorsanız stok için tek bir doğruluk kaynağı (source of truth) olmalıdır: ERP, sipariş yönetim sistemi (OMS) ya da e-ticaret altyapınız. Kanallar bu kaynaktan beslenir; hiçbir kanalda stok elle düzeltilmez.
  • Olay tabanlı itme: Her stok değişikliğinde ilgili SKU için tüm kanallara bir güncelleme kuyruğa yazılır. Kısa sürede gelen değişiklikler birleştirilir, API hız sınırlarına uyulur, hata alan istekler artan beklemeyle (exponential backoff) yeniden denenir. Kanala fark (delta) yerine, gönderim anında okunan güncel satılabilir adedi mutlak sayı olarak gönderin; böylece yeniden denenen bir istek aynı adedi ikinci kez düşmez.
  • Hızlı sipariş çekme: Pazaryeri siparişleri ne kadar geç çekilirse fazla satış riski o kadar artar. Webhook varsa kullanın, yoksa siparişleri sık aralıklarla çekin ve gelen siparişi hemen rezerve edin. Trendyol tarafındaki uç noktaları Trendyol API entegrasyonu yazımızda anlattık.
  • Kanal tamponu: Senkronizasyon gecikmesini karşılamak için pazaryerine satılabilir stoktan birkaç adet eksik gönderin. Az kalan ürünlerde (ör. satılabilir stok 2 ve altı) ürünü yalnızca kendi sitenizde açık tutmak da bir seçenektir.
  • Gece mutabakatı: Kanallardaki stoğu beklenen değerle karşılaştıran bir iş, olay akışında kaybolan güncellemeleri yakalar.

Güvenlik stoğu, yeniden sipariş noktası ve sipariş miktarı​

Ne zaman ve ne kadar sipariş verileceği göz kararına değil, basit bir hesaba dayanmalıdır. Talebin dalgalı, tedarik süresinin sabit olduğu ve günlük satışların normal dağılıma uyup günden güne bağımsız olduğu varsayımıyla yaygın kullanılan formüller şunlardır:
Code:
Güvenlik stoğu (SS)           = Z × σd × √L
Yeniden sipariş noktası (ROP) = d × L + SS

d  : ortalama günlük satış (adet)
σd : günlük satışın standart sapması (adet)
L  : tedarik süresi (gün)
Z  : hizmet düzeyi katsayısı (%90 ≈ 1,28; %95 ≈ 1,645; %98 ≈ 2,05; %99 ≈ 2,33)
Örnek: Günde ortalama 12 adet satılan, günlük satışının standart sapması 5 adet olan ve tedarikçiden 9 günde gelen bir ürün için %95 hizmet düzeyinde SS = 1,645 × 5 × √9 ≈ 24,7; yukarı yuvarlanınca 25 adettir. ROP = 12 × 9 + 25 = 133 adettir. Stok pozisyonu (satılabilir + yoldaki) 133'e ya da altına indiğinde yeni sipariş verilir.

Buradaki hizmet düzeyi, bir tedarik döngüsünü stoksuz kalmadan geçirme olasılığıdır (cycle service level); talebin yüzde kaçının doğrudan stoktan karşılandığını gösteren karşılama oranı (fill rate) ile karıştırmayın. Hizmet düzeyini her üründe aynı da seçmeyin: %95'ten %99'a çıkmak güvenlik stoğunu yaklaşık %41 artırır (2,33 ÷ 1,645). Yüksek düzeyi çok satan ve kâr getiren ürünlere ayırmak sermayeyi daha verimli kullanır.

Tedarik süresi de dalgalanıyorsa genişletilmiş formül kullanılır: SS = Z × √(L × σd² + d² × σL²); burada σL, tedarik süresinin gün cinsinden standart sapmasıdır. Aynı örnekte σL = 2 gün olursa SS ≈ 1,645 × √(9 × 25 + 144 × 4) ≈ 47 adete çıkar. Yani tedarikçinin teslim tutarlılığı, talepteki dalgalanma kadar önemlidir. Kampanya dönemlerinde ve sezonluk ürünlerde bu formüller de tek başına yetmez; geçmiş kampanya verisi ve tedarikçinin gerçek teslim performansı hesaba katılmalıdır.

Sipariş miktarı için başlangıç noktası ekonomik sipariş miktarıdır (EOQ): Q = √(2 × D × S ÷ H). D yıllık talep (adet), S sipariş başına sabit maliyet (satın alma ekibinin emeği, nakliye, mal kabul), H ise bir adedi bir yıl elde tutmanın maliyetidir (sermaye, depo, sigorta, fire). Toplam maliyet optimum çevresinde yavaş değiştiği için EOQ'dan %20 sapmak, sipariş ve elde tutma maliyetlerinin toplamını %3'ten az artırır; sonucu koli içi adede, tedarikçinin asgari sipariş miktarına (MOQ) ya da fiyat kademelerine yuvarlamak bu yüzden genellikle sorun olmaz. Siparişi sabit günlerde veriyorsanız (ör. her pazartesi) sabit bir sipariş noktası yerine hedef stok düzeyi (order-up-to level) kullanılır: T = d × (R + L) + Z × σd × √(R + L). Burada R sipariş aralığıdır (gün); her siparişte stok pozisyonunu T'ye tamamlarsınız.

Önceliklendirme için ABC analizi kullanılır: ürünler ciroya katkısına göre sıralanır; cironun büyük kısmını (çoğunlukla yaklaşık %80'ini) getiren küçük grup A, sonrakiler B ve C olur. A ürünlerinde stoksuz kalmak pahalıdır; sayım ve tedarik takibi en sık onlarda yapılır.

İadeler, sayım ve muhasebeyle uyum​

İade gelen ürünü doğrudan satılabilir stoğa eklemeyin. Önce satışa kapalı bir karantina konumuna (ör. ayrı bir warehouse_id) alın; kontrolde sağlam çıkanları return_restock hareketiyle satış stoğuna aktarın, hasarlıları karantinadan düzeltme (adjustment) hareketiyle fire olarak düşün. Kargo ve iade süreçlerinin entegrasyonunu kargo API yazımızda ele aldık.

Yılda bir kez yapılan genel sayım, yıl içindeki kaymaları geç yakalar. Döngüsel sayımda (cycle counting) her gün ya da hafta küçük bir ürün grubu sayılır; A ürünleri daha sık, C ürünleri daha seyrek. Sayılan raftan aynı anda toplama yapılırsa sonuç bozulur: konumu kısa süreliğine toplamaya kapatın ya da sayım anındaki sistem değerini kaydedip aradaki hareketleri hesaba katın. Sayım farkı bir düzeltme hareketiyle ve gerekçesiyle kaydedilir. Sürekli fark çıkan ürünler süreçteki sorunu (yanlış toplama, kayıp, hatalı mal kabul) gösterir. Bilanço esasına göre defter tutan işletmelerde yıl sonu envanteri muhasebe açısından da gereklidir; e-ticaret stok kayıtlarınızın muhasebe kayıtlarıyla uyumlu olması için mali müşavirinizle birlikte çalışın.

İzlenecek metrikler​

MetrikHesapNe söyler
Fazla satış oranıStok nedeniyle iptal edilen sipariş ÷ toplam siparişSenkronizasyon ve rezervasyonun sağlığı
Stoksuz kalma oranıStoksuz geçen ürün-gün ÷ toplam ürün-günTedarik ve yeniden sipariş ayarları
Stok devir hızı (inventory turnover)Satılan malın maliyeti ÷ ortalama stok değeriSermayenin ne kadar hızlı döndüğü
Stok devir süresi (days of inventory)365 ÷ yıllık stok devir hızı (gün)Ölü stok riski
Satılma oranı (sell-through rate)Dönemde satılan ÷ dönemde gelen adetSezonluk ürünlerde alım doğruluğu
Sayım doğruluğu (inventory accuracy)Farksız sayılan kalem ÷ sayılan kalemSistemdeki sayıya güvenilip güvenilemeyeceği

Sık yapılan hatalar​

  • Tek bir stok sütunu tutmak ve rezerve adedi ayırmamak.
  • Stoğu sepete eklemede düşmek ya da hem ödemede hem sevkiyatta düşerek iki kez saymak.
  • "Önce oku, sonra yaz" ile rezervasyon yapmak: yoğun bir kampanyada aynı son ürün iki kez satılır.
  • Pazaryerinde stoğu elle güncellemek: bir sonraki senkronizasyon ya da sipariş sayıları yeniden bozar.
  • Kanallara mutlak adet yerine fark (delta) göndermek: yeniden denenen bir istek stoğu iki kez düşer.
  • İadeleri kontrol etmeden satılabilir stoğa eklemek.
  • Güvenlik stoğunu tüm ürünlerde aynı sabit sayı (ör. 5 adet) yapmak.

Sık sorulan sorular​

Stok ne zaman düşülmeli: sepette mi, siparişte mi, ödemede mi?
Sepette değil. Sipariş oluşturulurken süreli bir rezervasyon yapın, ödeme onayında rezervasyonu kalıcı hâle getirin ve eldeki stoğu sevkiyatta düşün. Böylece sitede gösterilen sayı doğru kalır, depodaki sayı da fiziksel gerçeği yansıtır.

Pazaryerinde neden stoktan fazla satış oluyor?
Çoğunlukla üç nedenin birleşimidir: siparişlerin geç çekilmesi, stok güncellemelerinin kuyrukta beklemesi ve kanal tamponunun olmaması. Sipariş çekme aralığını kısaltmak ve az kalan ürüne tampon koymak çoğu durumda sorunu belirgin biçimde azaltır.

Excel ile stok takibi yeterli mi?
Tek kanalda, az ürünle başlarken yeterli olabilir. Birden fazla satış kanalı, varyantlı ürünler ya da birden fazla depo devreye girdiğinde, eşzamanlı güncellemesi ve hareket kaydı olmayan bir tablo fazla satış ve sayım farkı üretir.

Güvenlik stoğunu hesaplamak için ne kadar veri gerekir?
Mevsimselliği olmayan ürünlerde birkaç aylık günlük satış verisi başlangıç için yeterlidir. Stoksuz kalınan günler talebi olduğundan düşük gösterir; bu günleri hesaptan çıkarın ya da ayrıca işaretleyin.

ERP ile e-ticaret sitesi arasında stok hangi yönde akmalı?
Doğruluk kaynağı hangisiyse ondan diğerlerine. Depo ve muhasebe ERP'deyse (ör. Logo, Mikro, Netsis) stok ERP'den siteye ve pazaryerlerine, siparişler de kanallardan ERP'ye akar. İki yönlü ve elle düzeltilen stok, senkronizasyon çakışmalarının en yaygın nedenidir.

Fazla satış olduysa ne yapmalı?
Önce ürünü taahhüt ettiğiniz teslim süresi içinde tedarik edip edemeyeceğinize bakın; Mesafeli Sözleşmeler Yönetmeliği'ne göre bu süre, tüketicinin isteği ya da kişisel ihtiyaçlarına göre hazırlanan mallar dışında, mal satışlarında otuz günü geçemez. Müşteriye durumu ve yeni tarihi hemen bildirin. Süresinde teslim edemezseniz müşteri sözleşmeyi feshedebilir ve tahsil edilen tüm ödemeler, fesih bildiriminin size ulaştığı tarihten itibaren on dört gün içinde iade edilmelidir. Pazaryeri siparişlerinde platformun kendi iptal kuralları da geçerlidir; somut durumlar için hukuk danışmanınıza başvurun.

Sonuç ve sonraki adımlar​

  1. Eldeki, rezerve ve satılabilir stoğu ayırın; sitede ve kanallarda yalnızca satılabilir stoktan türetilen sayıyı gösterin.
  2. Stok değişikliklerini hareket kaydına çevirin ve gece mutabakatı kurun.
  3. Rezervasyonu tek koşullu sorguyla yapın; ödeme onayını, süre dolumunu ve iptali koşullu durum geçişleriyle yönetin.
  4. A grubu ürünler için güvenlik stoğu ve yeniden sipariş noktası hesaplayın, sonuçları birkaç ay izleyin.
  5. Fazla satış oranını haftalık takip edin; sıfıra yakın değilse önce sipariş çekme sıklığına ve kanal tamponuna bakın.
Stok senkronizasyonu ve pazaryeri entegrasyonlarını kurarken destek gerekirse AIOR'un stok senkronizasyon hizmetine göz atabilirsiniz.

What a stock error costs​

In e-commerce a stock figure is not just the count in the warehouse; it is a promise made at the same time on your own site, on marketplaces and on orders being picked. Get it wrong and the losses take three forms:
  • Overselling: selling items you do not have. The order is cancelled, the customer may not come back, and on marketplaces seller-caused cancellations also feed into your seller performance metrics. In Türkiye there is a legal side too: under Article 16 of the Distance Contracts Regulation (Mesafeli Sözleşmeler Yönetmeliği), goods being out of stock do not count as performance becoming impossible, so cancelling an order as "out of stock" carries legal risk.
  • Stockouts: a product runs out just when it sells best. Ad spend is wasted, and the visibility the product had built up in search and marketplace listings can fade.
  • Dead stock: capital tied up in items that do not sell, storage costs, and eventually clearing them at a discount.
This article describes a setup that handles all three: clear stock concepts, a data model built on a movement ledger, reservations in the checkout flow, multichannel synchronisation, safety stock, reorder points and order quantities, returns, counting and the metrics worth watching.

Four numbers: on hand, reserved, available, incoming​

A single "stock" column is behind most problems. Separate at least these four:
ConceptDefinitionWhat changes it
On handUnits physically in the warehouseGoods receipt, shipment, return, count adjustment
ReservedUnits ordered but not yet shippedOrder, cancellation, reservation expiry, shipment
Available to sellOn hand − reserved − sell bufferCalculated, never typed in
IncomingUnits ordered from a supplier but not yet receivedPurchase order, goods receipt
The figure shown on your site and on marketplaces is always derived from available to sell, never from on hand. On hand is for the warehouse and accounting; incoming feeds the reorder calculation and lets you open pre-orders or show an estimated delivery date. The sell buffer and the safety stock described further down are different things: the buffer is a few units kept off sale to absorb counting errors, while safety stock is part of the reorder calculation.

Data model: a ledger, not a counter​

When a counter that says "stock = 17" turns out to be wrong, nobody can tell why. Write every change as a movement row instead; on hand is the sum of the movements. The examples use MySQL syntax:
SQL:
CREATE TABLE stock_movements (
  id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  sku          VARCHAR(64) NOT NULL,
  warehouse_id INT NOT NULL,
  qty          INT NOT NULL,          -- positive in, negative out
  reason       VARCHAR(32) NOT NULL,  -- receipt, shipment, return_restock, adjustment, transfer
  ref          VARCHAR(64),           -- order, purchase order or count number
  created_by   VARCHAR(64) NOT NULL,
  created_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  KEY idx_sku_wh (sku, warehouse_id)
);

-- On hand = sum of movements
SELECT sku, warehouse_id, SUM(qty) AS on_hand
  FROM stock_movements
 GROUP BY sku, warehouse_id;
For performance you can also keep on-hand and reserved quantities in a stock_levels table with a unique key on (sku, warehouse_id). Treat it as a cache: update it in the same transaction as the movement, and have a nightly reconciliation job check that on hand equals the sum of movements and reserved equals the sum of open reservations. If they differ, raise an alert rather than fixing it silently. For products with variants, stock always lives at the variant SKU level (size, colour); the parent product is only a grouping.

Reservations in checkout and the race condition​

When two customers try to buy the last item in the same second, "read the stock, then decrement it" sells to both. Combine the check and the reservation in one conditional UPDATE:
SQL:
-- Reserve only if enough is available: one statement, no race
UPDATE stock_levels
   SET reserved = reserved + :qty
 WHERE sku = :sku
   AND warehouse_id = :wh
   AND on_hand - reserved - sell_buffer >= :qty;
-- 1 affected row means reserved, 0 means not enough stock
Why this is safe: when two transactions update the same row, the second waits for the first to finish, and the condition is then evaluated against the row's current values. MySQL (InnoDB) and PostgreSQL both behave this way at their default isolation levels (REPEATABLE READ and READ COMMITTED respectively). Under REPEATABLE READ or SERIALIZABLE in PostgreSQL the second transaction gets a serialization error instead and has to be retried. For orders with several lines, reserve every line in one transaction, update rows in a consistent order (by SKU, say) to reduce deadlocks, and roll back if any line returns 0.

The life cycle of a reservation:
  1. When the order is created: reserve with the statement above and, in the same transaction, write a reservations row (status pending, with an expires_at time).
  2. When payment is confirmed: set the status to confirmed with a conditional update (WHERE id = :id AND status = 'pending'); the time limit no longer applies. If no row is affected, the reservation has just expired, so take the late-payment path described below.
  3. When it expires or the order is cancelled: make that status change with one conditional statement too (status = 'pending' for expiry, status IN ('pending', 'confirmed') for a cancellation) and release the reserved quantity only if exactly one row was affected. If payment confirmation and expiry run at the same moment, only one of them succeeds, so a paid order never loses its stock; and a cancellation that collides with expiry cannot release the stock twice.
  4. On shipment: decrement on hand and reserved together and write a shipment movement.
Do not reserve stock when an item is added to the cart: abandoned carts would lock it. We cover measuring and recovering abandoned carts in our cart abandonment article. Off-the-shelf platforms work along the same lines: WooCommerce's Hold stock (minutes) setting holds stock for unpaid orders for that many minutes, after which an order still in Pending payment is cancelled and its stock goes back on sale. Orders placed with offline payment methods such as bank transfer usually go to On hold instead, where stock is already reduced and the timer does not apply, so you need your own rule for closing the unpaid ones.

The reservation window depends on the payment method: minutes are enough for card payments, while for bank transfer orders your own business rule decides. State that window, and what happens when it runs out, in your terms of sale. If a payment arrives after the reservation expired, reserve again if stock is still there; otherwise tell the customer straight away and refund, or, with the customer's agreement, turn the order into a dated pre-order.

Variants, bundles and multiple warehouses​

Unless a bundle is pre-packed and stored as its own SKU, it has no stock of its own; its availability is calculated from the components. Divide each component's available quantity by the number needed per bundle, round down, and take the smallest result. Example: a camping bundle holds 1 tent and 2 mats, so with 7 tents and 10 mats you can sell at most min(7, 5) = 5 bundles. When a bundle sells, the movements are written against the components.

With several warehouses, availability is calculated per warehouse, and which warehouse fulfils an order (the nearest, the one that can ship it in a single parcel, the oldest stock) is a separate rule. If you expose store stock to online orders (click and collect, for example), count accuracy in stores is usually lower than in the warehouse, so give store stock a larger sell buffer.

Selling on several channels: site, marketplaces and sync​

If you sell the same product on your own site and on Trendyol, Hepsiburada or Amazon, stock needs a single source of truth: your ERP, an order management system (OMS) or your store platform. Every channel is fed from it, and nobody corrects stock by hand on a channel.
  • Event-driven push: every stock change queues an update for that SKU to every channel. Changes that arrive close together are merged, API rate limits are respected, and failed requests are retried with exponential backoff. Send the channel the current available quantity, read at the moment of sending, as an absolute number rather than a delta, so a retried request cannot subtract the same units twice.
  • Fast order pulls: the later you pull marketplace orders, the higher the risk of overselling. Use webhooks where they exist; otherwise pull orders at short intervals and reserve each one as soon as it arrives. We describe the Trendyol side in our Trendyol API integration article.
  • Channel buffer: send marketplaces a few units less than your available stock to cover sync delays. For items running low (for example 2 or fewer available) keeping them on sale only on your own site is another option.
  • Nightly reconciliation: a job that compares each channel's stock with the expected value catches updates lost in the event stream.

Safety stock, reorder point and order quantity​

When and how much to order should come from a simple calculation, not a hunch. Assuming variable demand, a fixed lead time, and daily sales that are normally distributed and independent from one day to the next, the commonly used formulas are:
Code:
Safety stock (SS)    = Z × σd × √L
Reorder point (ROP)  = d × L + SS

d  : average daily sales (units)
σd : standard deviation of daily sales (units)
L  : lead time (days)
Z  : service level factor (90% ≈ 1.28; 95% ≈ 1.645; 98% ≈ 2.05; 99% ≈ 2.33)
Example: a product selling 12 units a day on average, with a standard deviation of daily sales of 5 units and a 9-day supplier lead time, needs SS = 1.645 × 5 × √9 ≈ 24.7, rounded up to 25 units, at a 95% service level, and ROP = 12 × 9 + 25 = 133 units. When the inventory position (available plus incoming) falls to 133 or below, you place the next order.

The service level here is the probability of getting through one replenishment cycle without a stockout (cycle service level); do not confuse it with the fill rate, the share of demand served straight from stock. Nor should one service level apply to everything: going from 95% to 99% raises safety stock by about 41% (2.33 ÷ 1.645), so reserving high levels for best-selling, profitable items uses capital better.

If the lead time varies as well, use the extended formula SS = Z × √(L × σd² + d² × σL²), where σL is the standard deviation of the lead time in days. In the same example, σL = 2 days pushes SS to about 1.645 × √(9 × 25 + 144 × 4) ≈ 47 units: a supplier's delivery consistency matters as much as swings in demand. During campaigns and for seasonal items even these formulas are not enough on their own; bring in past campaign data and the supplier's actual delivery record.

For how much to order, the economic order quantity (EOQ) is the usual starting point: Q = √(2 × D × S ÷ H), where D is annual demand in units, S the fixed cost per order (purchasing effort, freight, goods receipt) and H the cost of holding one unit for a year (capital, storage, insurance, shrinkage). Total cost changes slowly around the optimum, so ordering 20% more or less than the EOQ raises the sum of ordering and holding costs by less than 3%; rounding to case packs, the supplier's minimum order quantity (MOQ) or price breaks is therefore usually fine. If you order on fixed days (every Monday, say), use an order-up-to level instead of a fixed reorder point: T = d × (R + L) + Z × σd × √(R + L), where R is the review interval in days, and top the inventory position up to T with each order.

ABC analysis helps you prioritise: rank products by revenue contribution; the small group that brings in most of the revenue (often around 80%) is A, the rest B and C. Stockouts on A items are expensive, so they get the most frequent counts and the closest supply tracking.

Returns, counting and keeping accounting in line​

Do not put returned items straight back into available stock. Book them into a quarantine location that does not count towards availability (a separate warehouse_id, for example); after inspection, move the sound ones to sellable stock with a return_restock movement and write the damaged ones off from quarantine with an adjustment movement. We cover integrating shipping and returns in our cargo API article.

A once-a-year full count catches drift late. With cycle counting you count a small group of products every day or week: A items more often, C items less often. Picking from a location while it is being counted spoils the result, so either block the location for picking briefly or record the system figure at the moment of counting and account for the movements in between. Count differences are booked as adjustment movements with a reason, and products that keep showing differences point to a process problem (wrong picks, losses, sloppy goods receipt). In Türkiye, businesses keeping balance-sheet-basis books also need a year-end inventory for accounting; work with your accountant so that your e-commerce stock records match the books.

Metrics to watch​

MetricCalculationWhat it tells you
Oversell rateOrders cancelled for lack of stock ÷ all ordersHealth of sync and reservations
Stockout rateProduct-days out of stock ÷ all product-daysSupply and reorder settings
Inventory turnoverCost of goods sold ÷ average inventory valueHow fast capital turns over
Days of inventory365 ÷ annual inventory turnoverDead stock risk
Sell-through rateUnits sold ÷ units received in the periodBuying accuracy for seasonal items
Inventory accuracyLines counted without a difference ÷ lines countedWhether the system figure can be trusted

Common mistakes​

  • Keeping a single stock column with no separate reserved quantity.
  • Decrementing stock when an item is added to the cart, or decrementing at payment and again at shipment.
  • Reserving with "read, then write": in a busy campaign the same last item sells twice.
  • Updating stock by hand on a marketplace: the next sync or the next order breaks the figures again.
  • Sending channels deltas instead of absolute quantities: a retried request subtracts the stock twice.
  • Putting returns back into available stock without inspection.
  • Using one fixed safety stock (say 5 units) for every product.

Frequently asked questions​

When should stock be decremented: in the cart, at the order or at payment?
Not in the cart. Create a time-limited reservation when the order is placed, make it permanent when payment is confirmed, and decrement on hand at shipment. The figure on your site stays correct and the warehouse figure matches physical reality.

Why does overselling happen on marketplaces?
Usually because of three things together: orders pulled late, stock updates waiting in a queue, and no channel buffer. Shortening the order pull interval and buffering low-stock items cuts it down considerably in most cases.

Is a spreadsheet good enough for inventory tracking?
It can be when you start on one channel with a small catalogue. Once there are several sales channels, products with variants or more than one warehouse, a sheet without concurrent updates or a movement history produces overselling and count differences.

How much data do I need to calculate safety stock?
For non-seasonal products a few months of daily sales are enough to start. Days spent out of stock make demand look lower than it was; exclude them from the calculation or flag them separately.

Which way should stock flow between the ERP and the online store?
From whichever system is the source of truth to the others. If warehouse and accounting live in the ERP (Logo, Mikro or Netsis in many Turkish businesses), stock flows from the ERP to the site and the marketplaces, and orders flow from the channels into the ERP. Two-way stock with manual corrections is the most common cause of sync conflicts.

What should I do after an oversell?
First check whether you can source the item within the delivery time you promised; in Türkiye the Distance Contracts Regulation caps that time at thirty days for goods, except goods prepared to the consumer's request or personal needs. Tell the customer what happened and give a new date straight away. If you cannot deliver in time, the customer may terminate the contract, and everything collected must be refunded within fourteen days of the termination notice reaching you. Marketplace orders are also subject to the platform's own cancellation rules; take specific cases to your legal adviser.

Conclusion and next steps​

  1. Separate on hand, reserved and available, and show only figures derived from available stock on your site and channels.
  2. Turn stock changes into ledger movements and set up a nightly reconciliation.
  3. Reserve with a single conditional statement, and handle payment confirmation, expiry and cancellation as conditional status transitions.
  4. Calculate safety stock and reorder points for your A items and watch the results for a few months.
  5. Track the oversell rate weekly; if it is not close to zero, look at the order pull interval and the channel buffer first.
If you need help with stock synchronisation and marketplace integrations, have a look at AIOR's inventory sync service.
 
Last edited:

Forum statistics

Threads
925
Messages
932
Members
26
Latest member
Jessicawatson

Members online

No members online now.

Featured content

AIOR
AIOR TECHNOLOGY

Get a quote for all your needs

Hosting · Domain · Servers · Design · Software · Engineering · Industry Solutions

Get a quote

24/7 Support · Quick response

Back
Top