Bir satış temsilcisi şehrin kenarındaki bir hırdavatçıya giriyor, sinyal tek çizgiye düşüyor ve dükkân sahibi siparişini hemen vermek istiyor. Uygulama o anda dönen bir bekleme simgesi gösterirse temsilci kalem kâğıda döner; ertesi gün de merkezde biri o siparişi ERP'ye yeniden yazar.
BernSFA, BernSoftware'de tam da bu temsilci için geliştirdiğimiz saha satış (SFA) platformu: rota, ziyaret, sipariş, tahsilat ve araçtan satış için bir iOS ve Android uygulaması, yöneticiler için rol tabanlı bir web paneli ve müşterinin ERP'siyle çift yönlü eşitleme. Türkiye'de bu ERP genellikle Logo, Mikro, Netsis ya da SAP oluyor. En çok önem taşıyan mimari kararlar şunlardı.
Çevrimdışı olmak hata durumu değil, olağan durumdur
İlk karar geri kalan her şeyi belirliyor: uygulama sunucuyu hiçbir zaman beklemez. Sipariş girişi, müşteri kartları ve saha formları (anketler, raf ve fiyat kontrolleri, fotoğraflar, imzalar) bağlantı olmadan çalışır ve bağlantı geri geldiğinde eşitlenir.
Bu yüzden cihaz bir önbellek değil, gerçek bir yerel veritabanı tutar (bizim durumumuzda SQLite). Arayüz yalnızca yerel veriden okur ve yerel veriye yazar; eşitleme, ekranların hiç beklemediği bir arka plan işidir. Tasarımı sahadaki ortalama duruma göre değil, gördüğünüz en uzun sinyalsiz süreye göre yapın.
Veriyi sahibine göre ayırın
Sahiplik netleşince eşitleme çakışmalarının çoğu kendiliğinden ortadan kalkar. Biz veriyi üçe ayırıyoruz:
- ERP'ye ait ana veriler: ürünler, fiyat listeleri, cariler, bakiyeler, stok ve kampanyalar. Cihazda bunların salt okunur bir kopyası durur; birleştirilecek bir şey yoktur.
- Cihaza ait işlemler: siparişler, tahsilatlar, ziyaret girişleri ve formlar. Cihazda oluşturulur, sonradan üzerinde düzenleme yapılmaz ve merkeze yalnızca bir kez gönderilir. Bir düzeltme, kaydı değiştirmek değil, yeni bir kayıttır (iade ya da iptal).
- Ortak kayıtlar: çoğunlukla müşteri kartındaki değişiklikler, örneğin yeni bir telefon numarası. Gerçek çakışma riski yalnızca bunlarda vardır; bu yüzden temsilcinin en son gördüğü sürüme göre, alan düzeyinde değişiklik talepleri olarak gönderilirler. Kayıt o arada değişmişse talep, daha yeni verinin üzerine yazmak yerine incelemeye düşer.
Sizi koruyan kural şu: cihazın ana verinin üzerine yazmasına asla izin vermeyin; sunucunun da bir işlemi sessizce yeniden yazmasına asla izin vermeyin.
Zayıf sinyalde de ayakta kalan bir eşitleme kuyruğu
Her işlem, onu kaydeden yerel veritabanı işlemi (transaction) içinde aynı anda bir giden kutusu (outbox) tablosuna da yazılır. Arka planda çalışan bir işçi bu tabloyu boşaltır. Basitleştirilmiş hâli:
type OutboxItem = {
id: string; // istemcide üretilen UUID = idempotency anahtarı
kind: "order" | "collection" | "visit" | "form";
payload: unknown;
attempts: number;
nextAttemptAt: number; // epoch ms
status: "pending" | "sent" | "failed";
};
async function drainOutbox(db: LocalDb, api: Api) {
const due = await db.outbox.due(Date.now()); // bekleyenler, oluşturulma sırasına göre
for (const item of due) {
try {
await api.post(`/sync/${item.kind}`, item.payload, {
headers: { "Idempotency-Key": item.id },
});
await db.outbox.markSent(item.id);
} catch (err) {
if (isPermanent(err)) { // doğrulama hatası: bir insanın bakması gerekiyor
await db.outbox.markFailed(item.id, err);
continue;
}
const delay = Math.min(2 ** item.attempts * 5_000, 15 * 60_000);
await db.outbox.reschedule(item.id, item.attempts + 1, Date.now() + delay);
break; // ağ büyük olasılıkla kapalı; dur ve sonra yeniden dene
}
}
}
Göründüğünden daha önemli olan ayrıntılar:
- Kimlik cihazda üretilir. Sonraki her adımda idempotency anahtarı, yani aynı kaydın ikinci kez işlenmesini engelleyen anahtar olarak kullanılır.
- Kayıtları sırayla işleyin. Bir siparişe bağlı tahsilat, o siparişten önce ulaşmamalı.
- Geçici hatayı kalıcı hatadan ayırın. Zaman aşımında bekleme süresi her denemede uzatılarak tekrar denenir; kapanmış bir cari gibi bir doğrulama hatası ise temsilcinin görebildiği "ilgilenilmesi gerekenler" listesine alınır. Böyle bir kaydı durmadan yeniden denemek yalnızca sorunu gizler.
- Eşitleme durumunu gösterin. Küçük bir "3 kayıt gönderilmeyi bekliyor" rozeti, sessizlikten çok daha fazla güven verir.
ERP entegrasyonu: idempotent, asenkron ve sıkıcı
Arka ucumuz (Node.js ve PostgreSQL) cihazlarla ERP arasında durur; cihazlar ERP ile hiçbir zaman doğrudan konuşmaz. ERP'ye çoğu zaman yalnızca müşterinin kendi ağı içinden erişilebilir, arayüzler birbirinden çok farklıdır (REST, SOAP/XML, Logo'nun kendi API katmanı) ve ERP de tıpkı sinyali kesilen bir telefon gibi bakım için kapanır. Bu yüzden arka uç ikinci bir giden kutusu gibi çalışır:
- Sipariş idempotency anahtarıyla gelir, PostgreSQL'e kaydedilir ve alındığı hemen bildirilir.
- Bir entegrasyon işçisi siparişi her ERP için ayrı yazılmış bir bağdaştırıcı (adapter) üzerinden gönderir: Logo, Mikro, Netsis, SAP ya da genel REST/SOAP.
- Bağdaştırıcı kayıt eklemeden önce kontrol eder. Bizim sipariş kimliğimiz, ERP belgesinde bunun için ayrılan bir referans alanına yazılır ve ekleme öncesinde ilk olarak bu alanda aranır; böylece sonucu belirsiz kalan bir zaman aşımının ardından yapılan yeni deneme asla mükerrer kayıt oluşturmaz.
- ERP'nin verdiği belge numarası saklanır ve cihaza geri gönderilir; böylece temsilci yalnızca "gönderildi" değil, "ERP'ye işlendi" bilgisini görür.
Ana veriler ters yönde akar: stok, ürünler, cariler, fiyat listeleri ve kampanyalar ERP'den çekilir ve cihazlara her seferinde baştan indirilmek yerine yalnızca değişen kısımlarıyla gönderilir. Siparişler hemen merkeze gider; stok ve fiyatlar gibi toplu veriler ise her veri setinin ne kadar hızlı değiştiğine göre belirlenen aralıklarla eşitlenir. Her sipariş, fiyatlandırılırken kullanılan fiyat listesi sürümünü de kaydeder; sipariş ulaştığında daha yeni bir liste varsa fiyatı sessizce değiştirilmez, sipariş onay için işaretlenir.
Saha deneyimi: tek el, güçlü güneş, az dokunuş
İyi bir mimari, yavaş bir sipariş ekranını kurtarmaz. İlkelerimiz:
- Tek elle kullanım. Temsilciler çoğu zaman elinde bir katalog ya da koli tutar; bu yüzden ana işlemler ekranın altında, başparmağın uzanabileceği yerde durur.
- Güneş altında okunabilirlik. Yüksek kontrast, miktarlar ve toplamlar için büyük rakamlar; bir anlamı taşımak için asla yalnızca renge güvenmeyin.
- Birkaç dokunuşta sipariş. Son alınan ürünlerden başlayın, miktar için artı/eksi düğmeleri kullanın, iskonto kurallarını otomatik uygulayın.
- Ziyaret girişinde konumu doğrulayın. Ziyaretler sürekli takip yerine konumu doğrulanan bir girişle başlar; bu da pil tasarrufu sağlar.
Keşif, prototip, pilot, sonra yaygınlaştırma
Her kurulum dört adımı izler: önce keşif (satışın nasıl yürüdüğü, roller, iskontolar, onay kuralları ve ERP kurulumu); eşitleme kodu yazılmadan önce temsilcinin ekranlarından oluşan, tıklanarak gezilebilen bir prototip; tek bölgeyle ya da tek ekiple bir pilot; son olarak eğitimle birlikte bütün saha ekibine yaygınlaştırma.
Önce çevrimdışı çalışacak şekilde tasarlanmış bir sistem kendini pilotta kanıtlar. Bir kurulumda, eskiden Logo'ya yeniden yazılmak için bir iş günü bekleyen siparişler, telefon sinyal bulduğu anda ERP'ye ulaşmaya başladı; konum mantığında yapılan ayarlarla pil tüketimi de yaklaşık %45 azaldı. Küçük başlamak, bir hatanın bütün saha ekibine değil yalnızca bir bölgeye ulaşması demektir.
Veri müşteride kalır
Müşterinin adı, adresi, bakiyesi ve imzası, Türkiye'nin kişisel verileri koruma kanunu KVKK'ya göre kişisel veridir. Platform müşterinin kendi sunucularında ya da seçtiği bir bulutta çalışır; trafik şifrelidir, rol tabanlı yetkiler, denetim kayıtları ve yedekler vardır. Her cihazı yalnızca o temsilcinin ihtiyaç duyduğu veriyle sınırlamak, bir telefon kaybolduğunda açığa çıkabilecek veriyi de azaltır.
Kısaca
- Yerel veritabanı, arayüz için tek doğru kaynaktır; eşitleme arka planda çalışır.
- Veriyi sahibine göre ayırın; çakışma yönetimi yalnızca ortak kayıtlar için gerekir.
- Cihazda üretilen kimlikleri telefondan arka uca, oradan ERP'ye kadar idempotency anahtarı olarak kullanın.
- ERP'nin önüne de bir kuyruk koyun. Onun da tıpkı temsilcinin telefonu gibi kötü günleri olur.
- Herkese yaymadan önce tek bir ekiple pilot yapın.
Böyle bir proje mi planlıyorsun?
10 adımda planla