Ana içeriğe geç
Projelere Dön
Bergaz Operasyon Sistemi logo

Bergaz Operasyon Sistemi

İç Sistem

Kısaca

Önce
İşletme 2007'den beri tek bilgisayarda çalışan eski bir masaüstü programı kullanıyordu; geri kalan iş kâğıda ve elektronik tablolara dağılmıştı ve yönetici bir rakamı görmek için birinden rica etmek zorundaydı.
Sonra
Süt kabulü, üretici hesapları, kalite analizi ve stok artık dört ayrı web uygulamasında yürüyor; yönetici aynı bilgilere telefonundan, yalnızca gösteren ve hiçbir şeyi değiştiremeyen bir panelden ulaşıyor.
Ne değişti
Eski programdaki 19 yıllık kayıt, Türkçe karakterleri düzeltilerek yeni sisteme taşındı; kapanmış bir döneme sonradan kayıt girilemiyor ve makbuzdaki kuruşlar elle yapılan hesapla aynı kurala göre yuvarlanıyor.
Bergaz Operasyon Sistemi mimari şeması Bergaz Operasyon Sistemi Dört uygulama · dört veritabanı · tek operasyon Masaüstü sistem · Delphi / BDE / Paradox tek bilgisayar, dosya tabanlı tarihsel ETL Tek monolit değil — her uygulama kendi şeması, kendi migration ve yedek döngüsü. Muhasebe süt kabul · cari · makbuz argon2id Analiz kalite · üretim · kesinti Auth.js · bcrypt Stok hareket · anlaşma · sevk Auth.js · bcrypt Yönetim PWA · SALT-OKUNUR argon2id + PIN Neon Postgres muhasebe şeması Neon Postgres analiz şeması Neon Postgres stok şeması Neon Postgres yönetim şeması (auth) salt-okuma bağlantısı — yazan sorgu, sorgu kalkanında ağa çıkmadan reddediliyor argon2id — kendi yazdığım auth Auth.js · bcrypt tarihsel veri göçü salt-okuma okuma + yazma
Dört bağımsız Next.js uygulaması (muhasebe, analiz, stok, yönetim) dört ayrı Neon Postgres şeması üzerinde çalışır; aralarında foreign key, cross-DB sorgu veya senkronizasyon yoktur. Yönetim uygulaması diğer üç veritabanını yalnızca okur — yazan sorgu, sorgu kalkanında ağa çıkmadan reddedilir. Eski Delphi/Paradox masaüstü sisteminden veri tek yönlü tarihsel ETL ile taşınmıştır.

Bir mandıranın 2007'den beri kullandığı masaüstü programının yerine geçen dört parçalı sistem: süt kabulü ve üretici cari hesabı, kalite analizi, stok ve yöneticinin telefondan izlediği özet paneli. 19 yıllık veri eksiksiz taşındı.

Mağaza tarafı bu sistemin parçası değil, ayrı yürütülüyor — bkz. Bergaz Gıda e-ticaret vakası.

Kullanılan Teknolojiler

  • Next.js 16
  • TypeScript
  • Drizzle ORM
  • Neon Postgres
  • decimal.js
  • Zod
  • Serwist (PWA)
  • Python (Paradox ETL)

Öne Çıkan Özellikler

  • Dört uygulama, dört ayrı Neon veritabanı — sıfır cross-DB sorgu
  • Süt kabul, üretici cari, avans mahsuplaşması, dönem kapanışı ve makbuz
  • Kalite analizi — süt türüne göre referans aralıkları, kesinti snapshot'ı, dönem kilidi
  • Stok hareketleri, yıllık anlaşmalar ve sevk dağılımı
  • Telefon hedefli salt-okunur yönetici PWA'sı — yazan sorgu ağa çıkmadan reddediliyor
  • 19 yıllık Paradox verisinin iki aşamalı mojibake çözücüyle taşınması

Geliştirme Süresi

8 ay

Problem

İşletme 2007'den beri tek bilgisayarda çalışan bir masaüstü sistem kullanıyordu: Suttek.exe, Delphi/BDE üzerinde Paradox dosyaları. Geri kalan her şey kâğıda ve elektronik tablolara dağılmıştı. Sorunlar birbirini besliyordu — tek noktadan erişim, çok kullanıcı yok, mobil erişim yok, dosya tabanlı depolamanın bozulma riski (veri taşıma sırasında bir tablonun kayıt sayısı iki farklı okumada tutmadı) ve muhasebe programına makbuzların elle aktarılması. Bunların üstünde daha sessiz bir sorun vardı: mevcut ekranların tamamı operatör için tasarlanmıştı — yoğun form, çok sekme, klavye kısayolu. Yönetici bu ekranlardan bilgiye ulaşamıyordu; sorusunun cevabı sistemde duruyordu ama ona ulaşmanın yolu bir başkasından rica etmekti.

Kullanıcılar

Dört rol, dört farklı ihtiyaç. Operatör: gün içinde çok sayıda tekrarlı süt kabul kaydı giriyor, mükerrer kayıt riski yüksek, hız her şeyden önemli. Muhasebeci: avans mahsuplaşması, ay kapanışı, kesinti hesabı ve makbuz üretiyor — kuruş hatası kabul etmiyor. Kalite sorumlusu: numune değerlerinin referans dışına çıktığını kaydı bitirmeden görmek zorunda ve referans aralığı süt türüne göre değişiyor. Yönetici: teknolojiye yatkın değil, telefondan bakıyor, az sayıda ekran ve büyük rakam istiyor — ve sisteme yanlışlıkla dokunma ihtimalinin sıfır olmasını istiyor.

Rolüm

Tek geliştirici, dört repo. Sistem sınırlarının çizilmesi, dört veri modeli ve migration'ları, Paradox ETL hattı ve doğrulaması, para ve kesinti hesap motorları, iki ayrı auth katmanı, operatör panelleri, yönetici PWA'sı ve dağıtım.

Mimari

Dört bağımsız Next.js 16 App Router uygulaması, dört ayrı Neon Postgres veritabanı. Aralarında foreign key, cross-DB sorgu ve senkronizasyon mekanizması yok. Yazma yolu her uygulamada aynı: Server Action → Zod doğrulama → db.transaction(). Okuma yolu Server Component → sorgu katmanı. Muhasebe tarafında avans mahsuplaşması FIFO ve tek transaction içinde SELECT ... FOR UPDATE ile yürüyor; süt girişi (üretici, tarih) çiftine alınan advisory lock ile korunuyor; makbuz numarası Postgres SEQUENCE'inden geliyor. Analiz tarafında kesinti parametreleri kayıt anında satıra kopyalanıyor (deduction snapshot), dönem COMPLETED → LOCKED ile kilitleniyor ve kilitli dönem hem numune hem üretim kayıtlarına kapanıyor; günlük aggregate tablosu yazma anında güncelleniyor, böylece bölge ve üretici analizleri geçmişin tamamını yeniden taramıyor. Yönetim uygulaması dört bağlantı taşıyor: kendi auth veritabanına okuma+yazma, diğer üçüne salt-okuma. Diğer şemaların aynaları kolon kolon kopyalanıyor, index ve relation tanımları atılıyor; users ve audit_log hiçbir aynaya alınmıyor.

  1. Paradox (Delphi/BDE)Python ETL · tek yönlü
  2. Muhasebe DBMuhasebe · Next.js 16 App Router · okuma + yazmaYönetim uygulaması · salt-okuma
  3. Analiz DBAnaliz · Next.js 16 App Router · okuma + yazmaYönetim uygulaması · salt-okuma
  4. Stok DBStok · Next.js 16 App Router · okuma + yazmaYönetim uygulaması · salt-okuma
  5. Yönetim auth DBYönetim uygulaması · okuma + yazma
  6. Aralarında foreign key, cross-DB sorgu ve senkronizasyon mekanizması yok.
  7. yazma ▼ Server Action → Zod → db.transaction()
  8. okuma ▲ Server Component → sorgu katmanı
Dört bağımsız Next.js uygulaması dört ayrı Neon Postgres veritabanı üzerinde çalışır; veritabanları arasında hiçbir bağlantı yoktur ve yönetim uygulaması diğer üç veritabanını yalnızca okur.

Kritik Kararlar

  1. Dört ayrı uygulama, dört ayrı veritabanı — tek monolit değil

    Önceki tasarım iki sistemin ortak bir şemayı paylaşmasını öngörüyordu; bu kaldırıldı. Her uygulama kendi migration, yedek ve PITR döngüsünde bağımsız; biri bozulduğunda diğerleri etkilenmiyor ve geliştirme hızları ayrışabiliyor — analiz tarafı Auth.js ve bcrypt üzerinde kalırken muhasebe argon2id'e geçebildi. Sınır keyfi değil, iş sınırıyla çakışıyor: para bir tarafta, kalite ve üretim öbür tarafta.

    ÖdünleşimAynı kişi iki sistemde iki ayrı kayıt olarak duruyor; yeni bir üretici veya köy tanımı iki yere elle giriliyor ve eşleşme ad + köy üzerinden manuel. İki sistemi birleştiren bir rapor tek sorguyla alınamıyor — her uygulamadan ayrı ayrı dışa aktarıp dışarıda birleştirmek gerekiyor. Kullanıcılar her sisteme ayrı hesapla giriyor; tek oturum açma yok.

  2. Parada decimal.js ve ROUND_HALF_UP; JS Number yasak

    Postgres'ten gelen numeric kolonlar string olarak okunuyor ve hiçbir noktada Number'a çevrilmiyor; tüm aritmetik decimal.js ile tam precision yapılıyor. Yuvarlama yalnızca veritabanına yazma ve ekrana yazdırma anında, HALF_UP ile — banker's rounding değil, çünkü makbuzdaki kuruş elle yapılan hesapla birebir tutmak zorunda. Ayrıca yuvarlama birikimi ayrı bir kalem: mahsuplaşmada her satır için fiyat × kg yapılırsa son tahsis bir kuruş eksik kalabiliyor, son satırda kalan tutar üzerinden hesap yapılıyor.

    ÖdünleşimHer aritmetik ifade bir Decimal sarmalı gerektiriyor; `a + b` yazılamıyor, hesap kodu belirgin biçimde daha uzun ve okunması daha yorucu. Daha kötüsü, kural derleyici tarafından zorlanmıyor — bir yerde string'i Number'a çeviren tek satır sessizce çalışır ve hatayı ancak kuruş tutmadığında fark edersin.

  3. Yönetici uygulaması salt-okunur — veri girişi, düzenleme, silme yok

    Yöneticinin ihtiyacı yeni bir giriş ekranı değil, mevcut veriye okunabilir bir pencereydi; yazma yeteneği eklemek yalnızca yanlış dokunma riski getirirdi. Salt-okunurluğun nerede garanti altına alındığı ise asıl karar: plan bunu Neon tarafında ayrı bir read-only rol olarak öngörüyordu, ancak bağlantılar pratikte hâlâ sahip rolünde kaldığı için garanti uygulama içindeki sorgu kalkanına düştü. Kalkan, sorgunun `select` veya `with` ile başlamasını şart koşuyor ve yazan bir anahtar kelime geçerse sorgu ağa çıkmadan reddediliyor — yazılabilir CTE dahil.

    ÖdünleşimGaranti veritabanının değil uygulamanın içinde duruyor; kalkandan geçmeyen yeni bir sorgu yolu eklendiği anda koruma ortadan kalkıyor ve bunu hatırlatan hiçbir mekanizma yok. Kelime tabanlı reddetme ayrıca yanlış pozitif üretebiliyor: adında yasaklı bir kelime geçen meşru bir kolon ya da CTE sorguyu bloke ediyor, o noktada sorguyu kalkanın anlayacağı şekilde yeniden yazmak gerekiyor.

  4. 19 yıllık Paradox verisi ETL ile taşındı; Türkçe karakter doğrulaması ayrı bir adım

    Paradox okuyucusu byte akışını latin1 ve cp437 karışımı bir mojibake olarak veriyor; düz cp857 veya cp1254 çözümlemesi başarısız oluyor. Çözücü iki aşamalı: önce gözlenmiş bozulmaları Türkçe harflere eşleyen deterministik bir harita, ardından kalan yüksek byte'lar için cp1254 ile cp857 arasında skor yarışması. İçe aktarım tek başına yeterli sayılmadı — yıl bazında toplam karşılaştırması, sayım karşılaştırması ve ayrı bir doğrulama koşusu eklendi, çözülemeyen satırlar elle inceleme listesine düşürüldü.

    ÖdünleşimBirinci aşama gözleme dayalı bir harita: kaynakta daha önce görülmemiş bir bozulma çıktığında kendiliğinden genellemiyor, elle eklenmesi gerekiyor. İkinci aşama olasılıklı; iki kodlamanın da makul göründüğü kısa alanlarda yanlış tarafa düşebiliyor. Bu yüzden göç hiçbir zaman "çalıştır ve unut" olmadı, her koşu bir doğrulama turu ve insan kararı gerektirdi. Okuyucunun kendisi de x86_64 Python istiyor; Apple Silicon'da geliştirme ortamına Rosetta katmanı eklendi.

  5. Neon pooled/unpooled ayrımı — runtime pooled, migration unpooled

    Uygulama çalışma anında havuzlanmış bağlantıyı kullanıyor; Vercel'de her serverless fonksiyon örneğinin ayrı bağlantı açması yüzünden oluşan bağlantı patlamasını bu önlüyor. Migration ise doğrudan bağlantı istiyor: pgbouncer'ın transaction modu çok ifadeli migration'ı bozuyor. Bu yüzden iki ayrı bağlantı dizesi tutuluyor ve migration yolu, doğrudan bağlantı tanımlı değilse çalışmayı reddediyor.

    ÖdünleşimTek yerine iki bağlantı dizesi taşınıyor ve ikisinin de her ortamda (yerel, preview, production) doğru ayarlanması gerekiyor. Karıştırma hatası derleme anında değil çalışma anında ortaya çıkıyor — üstelik çoğu zaman migration'ın ortasında, yani en pahalı yerde.

Teknoloji Seçimi

Dört uygulamanın da Next.js 16 App Router üzerinde olması bilinçli: aynı yazma deseni (Server Action → Zod → transaction) dört repoda tekrar ettiği için bir repodan diğerine geçerken zihinsel model değişmiyor. Neon, dağıtımla aynı serverless modelde kalırken WebSocket sürücüsüyle gerçek transaction verebiliyor — FIFO mahsuplaşmadaki SELECT ... FOR UPDATE ve çok ifadeli yazmalar bunu zorunlu kılıyordu. Drizzle ORM SQL'e yakın duruyor: partial unique index, enum ve FK davranışı migration dosyasında açıkça görünüyor; veri bütünlüğünün merkezde olduğu bir sistemde kısıtların ORM soyutlamasının arkasında kalmaması önemliydi. decimal.js para hesabında float yuvarlamasını baştan dışarıda tutuyor. Zod tek şema olarak hem formda hem Server Action girdisinde çalışıyor. Yönetim tarafında Serwist, uygulamayı telefona kurulabilir hale getirip son görülen veriyi önbellekte tutuyor. ETL'in Python olmasının tek sebebi Paradox: çalışan tek okuyucu orada.

Güvenlik

İki ayrı auth yığını bilerek yan yana duruyor. Analiz ve stok tarafında Auth.js credentials + bcrypt, JWT session; yetki üç noktada denetleniyor — proxy katmanında optimistik rota koruması, layout seviyesinde tam rol doğrulaması ve Server Action içinde sahiplik kontrolü, böylece giriş personeli yalnızca kendi kayıtlarını düzenleyip silebiliyor. Muhasebe ve yönetim tarafında kendi yazdığım auth: argon2id (OWASP 2024 parametreleri), muhasebede dört rol (yönetici / muhasebe / operatör / görüntüleyici), yönetimde uzun ömürlü oturum ve altı haneli PIN. Audit log'da kimlik numarası, IBAN ve sigorta numarası gibi alanlar ham yazılmıyor, yalnızca "değişti" bilgisi tutuluyor; diğer tüm INSERT, UPDATE ve DELETE eski ve yeni değeriyle JSONB olarak loglanıyor ve eski kayıtlar batch'li bir komutla temizleniyor. Rate limiting girişte ve tüm yazma action'larında açık, silinmiş kullanıcı giriş yapamıyor, dinamik rota parametreleri UUID olarak doğrulanıyor. Dördü de kapalı sistem: robots.txt'te tümüne kapalı ve yanıt başlığında noindex. Yönetimden çıkan her sorgu ayrıca sorgu kalkanından geçiyor.

Performans

Bu dört uygulamanın hiçbiri halka açık değil, dolayısıyla saklanmış bir Lighthouse ölçümü yok ve olmayan bir skoru yazmıyorum; buradaki ölçü sayfa hızı değil sorgu davranışı. Analiz tarafında günlük aggregate tablosu yazma anında güncelleniyor, böylece bölge ve üretici analizleri geçmişin tamamını taramıyor; yavaş sorgular ayarlanabilir bir eşik üzerinden loglanıyor ve admin sorgularına EXPLAIN ANALYZE profili almak için repoda hazır bir komut var. Muhasebe tarafında materialized view kurulmadı ama karar açık bırakıldı: tarihsel verinin tamamı canlıya taşındıktan sonra p95 iki saniyeyi geçerse yeniden değerlendirilecek. Yönetim uygulaması yalnızca telefon için tasarlandı — 390px tasarım genişliği, breakpoint yok, 17px gövde ve 34–40px KPI rakamı, en az 48×48px dokunma hedefi; yakınlaştırma bilerek engellenmedi. Son görülen veri service worker önbellekte tutuluyor ve ekranda "kaç dakika önce güncellendi" damgası gösteriliyor.

Sonuçlar

Aktif geliştirme
2026-02 → 2026-09 · dört repoda 347 commit
TypeScript kaynak dosyası
802 — muhasebe 364 · yönetim 203 · analiz 160 · stok 75
Şema
Dört ayrı veritabanında 49 tablo · 30 Drizzle migration
Şema aynası
Yönetim uygulaması diğer üç veritabanından 24 tabloyu salt-okuma aynası olarak taşıyor
Test dosyası
9 — tamamı yönetim uygulamasında (sorgu kalkanı, stopaj, fiyat, sapma)
Yazma bütünlüğü
Analiz tarafında 17 yazma action'ının tamamı tek transaction — kısmi kayıt yok
Muhasebe garantisi
Kilitli dönem yazmaya kapalı; kesinti hesabı kayıt anındaki parametrelerle donduruluyor
Salt-okunurluk garantisi
Yönetimden çıkan sorgu `select` veya `with` ile başlamak zorunda; yazan sorgu ağa çıkmıyor

Bugün Ne Farklı Yapardım

Salt-okunurluk bugün uygulama katmanında bir sorgu kalkanı olarak duruyor, oysa doğru yeri veritabanı: Neon'da ayrı bir read-only rol açılsaydı garanti uygulamanın dışında olurdu ve kalkanı atlayan yeni bir sorgu yolu onu delemezdi. Aynı dersi bu sistemin içinde bir kez zaten almıştım — mükerrer numune kontrolünü form doğrulamasından çıkarıp partial unique index'e taşıdığımda kural artık hangi yoldan gelinirse gelinsin geçerliydi. Görev Kahramanı'nda da aynı desen eşzamanlılık tarafında çıkmıştı: ödül yazımını uygulama katmanında karşılaştırmalı güncellemeyle korumak, her yeni yazma yolunun aynı korumayı yeniden kurmasını gerektiriyor. Tekrarlayan sonuç şu: garanti uygulamada durduğu sürece her yeni yol onu yeniden doğrulamak zorunda, ve er geç biri unutuyor. İkinci nokta test: dört repodan yalnızca birinde otomatik test var. Para hesabı — stopaj, fiyat, sapma — saf fonksiyon ve test edilmesi en kolay yer; yönetimde oradan başladım ama muhasebede aynısını yapmadım. Bugün sırayı ters çevirir, testi en çok paranın döndüğü repoda yazardım.

İç sistem

Nereden başlayacağınızı birlikte bulalım

Ne yapmak istediğinizi anlatın; ne kadar süreceğini ve en mantıklı ilk adımı baştan söylerim.