- Konu sahibi
- #1
Satış faturası kaydedilirken stok hareketi oluşturmanın güvenli yolu, fatura başlığını ve fatura satırlarını tek veritabanı işlemi içinde kaydetmektir. Her satır için stok kartındaki miktarı, depo bilgisini ve birim dönüşümünü doğruladıktan sonra stok hareketine çıkış kaydı eklenir; işlem sırasında herhangi bir hata oluşursa fatura ve stok hareketleri birlikte geri alınır. Böylece kaydedilmiş görünen ancak stoğu düşmemiş bir fatura veya stoğu düşülmüş ancak faturası oluşmamış bir kayıt bırakılmaz.
Bir satış faturasını en az üç kayıt grubu ile düşünün:
Örneğin varsayımsal olarak 10 adet ürün satıldığında stok hareketindeki miktar genellikle
İşlemi başlatmadan önce fatura satırlarında şu bilgilerin geçerli olduğundan emin olun:
Delphi tarafında bağlantı nesneniz FireDAC, dbExpress veya başka bir erişim katmanı olabilir. Aşağıdaki örnek, FireDAC ve SQL parametreleri kullanan varsayımsal bir yapıdır. Tablo ve alan adlarını kendi veritabanınızdaki adlarla değiştirmeniz gerekir.
Aşağıdaki kod, bağlantı nesnesinin
Bu örnekte
Ayrıca örnekte fatura başlığından sonra satırlar ve stok hareketleri kaydedilmektedir. Fatura başlığı eklenirken oluşan hata, satır kaydındaki hata veya stok yetersizliği durumunda
Satış miktarı ile stoktan düşülecek miktar her zaman aynı birimde olmayabilir. Stok kartı koli üzerinden tutuluyor, fatura ise adet üzerinden kesiliyorsa önce birim dönüşümü yapılmalıdır. Örneğin bir kolide 12 adet bulunuyor ve faturada 2 koli satılıyorsa stok hareketi, stok bakiyesinin hangi birimde tutulduğuna göre 2 koli veya 24 adet olmalıdır.
Bu nedenle hareket kaydında mümkünse hem fatura miktarı hem de stok miktarı tutulabilir:
Örneğin 2 koli x 12 adet hesabıyla stok miktarı 24 adet çıkıyorsa stok hareketine 24 yazılır. Fatura satırında 2 koli tutulur. Böylece kullanıcı faturayı ticari birimle görürken stok hesabı temel stok birimiyle yapılır. Dönüşüm katsayısı kaydedilmezse daha sonra hareketin nasıl hesaplandığı açıklanamayabilir.
İki yaygın yaklaşım vardır. Birincisinde her hareket ayrı kaydedilir ve stok kartındaki bakiye alanı işlem sırasında güncellenir. Bu yöntem raporların hızlı çalışmasını sağlar; ancak bakiye alanı ile hareket toplamı arasında tutarsızlık oluşmaması için tüm stok işlemlerinin aynı kayıt prosedürünü kullanması gerekir.
İkincisinde bakiye, alış ve satış hareketlerinin toplamından hesaplanır. Bu yaklaşım hareket geçmişini temel kaynak yapar; fakat çok büyük veri kümelerinde rapor sorguları için ek indeksleme veya özetleme gerekebilir. Hangi yaklaşım seçilirse seçilsin, satış faturası kaydı sırasında stok hareketinin oluşturulması ve belgeyle ilişkilendirilmesi değişmez.
Stok kartındaki
Satış faturası iptal edildiğinde ilk faturanın stok hareketini silmek yerine ters yönlü bir hareket oluşturmak daha güvenlidir. Satışta 5 adet çıkış yapıldıysa iptal işleminde 5 adet giriş hareketi oluşturulabilir. Böylece hangi işlemle hangi hareketin düzeltildiği korunur.
İade faturası da aynı şekilde satış hareketinin karşılığı olan bir giriş hareketi oluşturur. Ancak iade edilen ürünün tekrar satılabilir durumda olup olmadığı, farklı bir depoya alınacağı veya kalite kontrol bekleyeceği gibi işletme kuralları varsa doğrudan satılabilir stok bakiyesine eklenmemelidir. Bu durumda iade deposu veya bekleyen stok türü kullanılmalıdır.
İptal ve iade işlemlerinde de başlık, satır, ters hareket ve bakiye güncellemesi tek işlem içinde yapılmalıdır. Eski hareketi fiziksel olarak silmek, denetim geçmişini ve raporların dayanağını zayıflatır; yalnızca açık bir veri düzeltme politikası varsa ve yetkili işlem olarak uygulanıyorsa tercih edilmelidir.
Kayıt başarılı olduğunda yalnızca ekranda “kaydedildi” mesajını görmek yeterli değildir. Aynı fatura kimliği üzerinden şu üç sonucu karşılaştırın:
Aynı faturayı ikinci kez kaydetme ihtimaline karşı
Kodda yalnızca fatura başlığı ve satırları kaydediliyor, stok hareketi veya bakiye güncellemesi çağrılmıyor olabilir. Aynı fatura kimliğiyle
Hem stok hareketine negatif miktar yazılıp hem de bakiye ayrıca azaltılıyorsa ve bakiye hareketlerden yeniden hesaplanıyorsa çift düşüm oluşabilir. Sistemde stok bakiyesinin doğrudan mı güncellendiğini, yoksa hareketlerden mi hesaplandığını kesinleştirin ve tek bir yöntemi uygulayın.
Ekran kontrolü yapılmış olsa bile işlem anında tekrar kontrol yapılmıyorsa eş zamanlı satışlar nedeniyle bu durum oluşabilir. Bakiye okuma, yeterlilik kontrolü ve güncelleme aynı işlem içinde yapılmalı; güncelleme beklenen mevcut bakiye koşuluyla gerçekleştirilmelidir. Sonuçta güncellenen satır sayısı beklenenden farklıysa işlem geri alınmalıdır.
Başlık, satır ve stok işlemleri farklı bağlantılarla veya farklı işlemler içinde çalıştırılıyorsa geri alma yalnızca son sorguyu etkileyebilir. Faturanın bütün kayıt işlemlerinde aynı bağlantı nesnesini ve aynı işlemi kullanın. Hata yakalandığında
Fatura kaydı tamamlandığında beklenen sonuç; her satırın faturaya, her stok hareketinin ilgili satıra ve her bakiye değişikliğinin doğru depoya bağlı olmasıdır. Bu ilişkiyi işlem bütünlüğü, birim dönüşümü, eş zamanlı stok kontrolü ve iptal-iade için ters hareket yaklaşımıyla birlikte kurarsanız satış faturası kaydı stok takibiyle tutarlı çalışır.
Fatura ve stok hareketi için temel veri yapısı
Bir satış faturasını en az üç kayıt grubu ile düşünün:
- Fatura başlığı: müşteri, tarih, belge numarası, depo ve toplam bilgilerinin tutulduğu kayıt.
- Fatura satırı: stok kodu, miktar, birim fiyat, KDV ve iskonto bilgilerinin tutulduğu kayıt.
- Stok hareketi: hangi stoktan, hangi depodan, hangi tarihte ve hangi belge nedeniyle ne kadar çıkış yapıldığını gösteren kayıt.
Örneğin varsayımsal olarak 10 adet ürün satıldığında stok hareketindeki miktar genellikle
-10 veya hareket türü ÇIKIŞ ve miktar 10 şeklinde tutulur. Hangi yöntemin kullanılacağı veri modeline bağlıdır; aynı sistemde hem negatif miktar hem de ayrıca “ÇIKIŞ” anlamı kullanılarak iki kez düşüm yapılmamalıdır.Kaydetmeden önce kontrol edilmesi gerekenler
İşlemi başlatmadan önce fatura satırlarında şu bilgilerin geçerli olduğundan emin olun:
- Stok kodu boş olmamalı ve stok kartında bulunmalıdır.
- Miktar sıfırdan büyük olmalıdır.
- Satırın bağlı olduğu depo belirli olmalıdır.
- Stok kartı satışa kapalıysa veya pasif durumdaysa satışa izin verilmemelidir.
- Stok yetersizliği işletme kuralına göre ya engellenmeli ya da eksi stoğa izin verilen ayrı bir politika uygulanmalıdır.
- Aynı fatura satırının ikinci kez kaydedilmesini önlemek için fatura kimliği ve satır kimliği hareket kaydına bağlanmalıdır.
Önerilen kayıt sırası
Delphi tarafında bağlantı nesneniz FireDAC, dbExpress veya başka bir erişim katmanı olabilir. Aşağıdaki örnek, FireDAC ve SQL parametreleri kullanan varsayımsal bir yapıdır. Tablo ve alan adlarını kendi veritabanınızdaki adlarla değiştirmeniz gerekir.
- Yeni bir veritabanı işlemi başlatın.
- Fatura başlığını kaydedin ve oluşan fatura kimliğini alın.
- Her fatura satırını kaydedin.
- Her satır için stok kartını ve mevcut depo miktarını doğrulayın.
- Stok hareketi kaydını oluşturun.
- Stok bakiyesini güvenli koşulla güncelleyin.
- Tüm adımlar başarılıysa işlemi onaylayın; herhangi bir hata varsa geri alın.
FireDAC ile örnek işlem akışı
Aşağıdaki kod, bağlantı nesnesinin
TFDConnection, fatura başlığının FATURA, satırların FATURA_SATIR, stok hareketlerinin ise STOK_HAREKET tablosunda tutulduğu varsayımsal bir örnektir. Kodun amacı işlem sırasını göstermektir; veritabanınızdaki kimlik üretme yöntemi ve SQL söz dizimi farklı olabilir.
Kod:
procedure TFaturaForm.FaturayiKaydet;
var
FaturaID: Integer;
SiraNo: Integer;
StokID: Integer;
DepoID: Integer;
Miktar: Double;
YeniBakiye: Double;
begin
if FaturaSatirlari.Count = 0 then
raise Exception.Create('Faturada en az bir satır bulunmalıdır.');
FDConnection1.StartTransaction;
try
FDQuery1.Close;
FDQuery1.SQL.Text :=
'insert into FATURA (MUSTERI_ID, TARIH, BELGE_NO, DEPO_ID) ' +
'values (:MUSTERI_ID, :TARIH, :BELGE_NO, :DEPO_ID) ' +
'returning ID';
FDQuery1.ParamByName('MUSTERI_ID').AsInteger := MusteriID;
FDQuery1.ParamByName('TARIH').AsDateTime := FaturaTarihi;
FDQuery1.ParamByName('BELGE_NO').AsString := BelgeNo;
FDQuery1.ParamByName('DEPO_ID').AsInteger := DepoID;
FDQuery1.Open;
FaturaID := FDQuery1.FieldByName('ID').AsInteger;
for SiraNo := 0 to FaturaSatirlari.Count - 1 do
begin
StokID := FaturaSatirlari[SiraNo].StokID;
DepoID := FaturaSatirlari[SiraNo].DepoID;
Miktar := FaturaSatirlari[SiraNo].Miktar;
if Miktar <= 0 then
raise Exception.CreateFmt(
'Geçersiz miktar. Satır: %d', [SiraNo + 1]);
FDQuery1.Close;
FDQuery1.SQL.Text :=
'select BAKIYE from STOK_BAKIYE ' +
'where STOK_ID = :STOK_ID and DEPO_ID = :DEPO_ID ' +
'for update';
FDQuery1.ParamByName('STOK_ID').AsInteger := StokID;
FDQuery1.ParamByName('DEPO_ID').AsInteger := DepoID;
FDQuery1.Open;
if FDQuery1.IsEmpty then
raise Exception.CreateFmt(
'Stok bakiyesi bulunamadı. Stok: %d, depo: %d',
[StokID, DepoID]);
YeniBakiye := FDQuery1.FieldByName('BAKIYE').AsFloat - Miktar;
if YeniBakiye < 0 then
raise Exception.CreateFmt(
'Yetersiz stok. Stok: %d, depo: %d', [StokID, DepoID]);
FDQuery2.Close;
FDQuery2.SQL.Text :=
'insert into FATURA_SATIR ' +
'(FATURA_ID, SIRA_NO, STOK_ID, DEPO_ID, MIKTAR, BIRIM_FIYAT) ' +
'values (:FATURA_ID, :SIRA_NO, :STOK_ID, :DEPO_ID, ' +
':MIKTAR, :BIRIM_FIYAT)';
FDQuery2.ParamByName('FATURA_ID').AsInteger := FaturaID;
FDQuery2.ParamByName('SIRA_NO').AsInteger := SiraNo + 1;
FDQuery2.ParamByName('STOK_ID').AsInteger := StokID;
FDQuery2.ParamByName('DEPO_ID').AsInteger := DepoID;
FDQuery2.ParamByName('MIKTAR').AsFloat := Miktar;
FDQuery2.ParamByName('BIRIM_FIYAT').AsFloat :=
FaturaSatirlari[SiraNo].BirimFiyat;
FDQuery2.ExecSQL;
FDQuery2.Close;
FDQuery2.SQL.Text :=
'insert into STOK_HAREKET ' +
'(FATURA_ID, STOK_ID, DEPO_ID, TARIH, TUR, MIKTAR) ' +
'values (:FATURA_ID, :STOK_ID, :DEPO_ID, :TARIH, ' +
':TUR, :MIKTAR)';
FDQuery2.ParamByName('FATURA_ID').AsInteger := FaturaID;
FDQuery2.ParamByName('STOK_ID').AsInteger := StokID;
FDQuery2.ParamByName('DEPO_ID').AsInteger := DepoID;
FDQuery2.ParamByName('TARIH').AsDateTime := FaturaTarihi;
FDQuery2.ParamByName('TUR').AsString := 'SATIS';
FDQuery2.ParamByName('MIKTAR').AsFloat := Miktar;
FDQuery2.ExecSQL;
FDQuery2.Close;
FDQuery2.SQL.Text :=
'update STOK_BAKIYE set BAKIYE = :BAKIYE ' +
'where STOK_ID = :STOK_ID and DEPO_ID = :DEPO_ID';
FDQuery2.ParamByName('BAKIYE').AsFloat := YeniBakiye;
FDQuery2.ParamByName('STOK_ID').AsInteger := StokID;
FDQuery2.ParamByName('DEPO_ID').AsInteger := DepoID;
FDQuery2.ExecSQL;
end;
FDConnection1.Commit;
except
FDConnection1.Rollback;
raise;
end;
end;
Bu örnekte
FOR UPDATE, ilgili stok bakiyesi satırının işlem tamamlanana kadar başka bir işlem tarafından değiştirilmesini önlemek için kullanılmıştır. Bu ifade her veritabanında aynı şekilde desteklenmeyebilir; kullandığınız veritabanının kilitleme ve işlem özelliklerine göre eşdeğer yöntem uygulanmalıdır.Ayrıca örnekte fatura başlığından sonra satırlar ve stok hareketleri kaydedilmektedir. Fatura başlığı eklenirken oluşan hata, satır kaydındaki hata veya stok yetersizliği durumunda
Rollback çalışır. Böylece işlem yarıda kaldığında yalnızca başlık kaydı kalmaz.Stok hareketi hangi miktarla oluşturulmalı?
Satış miktarı ile stoktan düşülecek miktar her zaman aynı birimde olmayabilir. Stok kartı koli üzerinden tutuluyor, fatura ise adet üzerinden kesiliyorsa önce birim dönüşümü yapılmalıdır. Örneğin bir kolide 12 adet bulunuyor ve faturada 2 koli satılıyorsa stok hareketi, stok bakiyesinin hangi birimde tutulduğuna göre 2 koli veya 24 adet olmalıdır.
Bu nedenle hareket kaydında mümkünse hem fatura miktarı hem de stok miktarı tutulabilir:
| Alan | Anlamı |
|---|---|
| Fatura miktarı | Müşteriye satılan miktar ve fatura birimi |
| Stok miktarı | Stok bakiyesinden düşülen dönüştürülmüş miktar |
| Birim katsayısı | Fatura biriminden stok birimine dönüşüm oranı |
| Hareket türü | Satış, iade, alış veya depo transferi gibi işlemin nedeni |
Stok kartını doğrudan azaltmak mı, hareketlerden bakiye hesaplamak mı?
İki yaygın yaklaşım vardır. Birincisinde her hareket ayrı kaydedilir ve stok kartındaki bakiye alanı işlem sırasında güncellenir. Bu yöntem raporların hızlı çalışmasını sağlar; ancak bakiye alanı ile hareket toplamı arasında tutarsızlık oluşmaması için tüm stok işlemlerinin aynı kayıt prosedürünü kullanması gerekir.
İkincisinde bakiye, alış ve satış hareketlerinin toplamından hesaplanır. Bu yaklaşım hareket geçmişini temel kaynak yapar; fakat çok büyük veri kümelerinde rapor sorguları için ek indeksleme veya özetleme gerekebilir. Hangi yaklaşım seçilirse seçilsin, satış faturası kaydı sırasında stok hareketinin oluşturulması ve belgeyle ilişkilendirilmesi değişmez.
Stok kartındaki
BAKIYE alanını güncelliyor ancak STOK_HAREKET tablosuna kayıt eklemiyorsanız, ekranda doğru miktarı görmeniz stok izlenebilirliğinin sağlandığını kanıtlamaz. Tersine yalnız hareket ekleyip bakiye alanını güncellemiyorsanız, bakiyeyi bu hareketlerden hesaplayan bir yapınız yoksa stok ekranı eski değeri göstermeye devam eder.İade, fatura iptali ve düzeltme nasıl ele alınmalı?
Satış faturası iptal edildiğinde ilk faturanın stok hareketini silmek yerine ters yönlü bir hareket oluşturmak daha güvenlidir. Satışta 5 adet çıkış yapıldıysa iptal işleminde 5 adet giriş hareketi oluşturulabilir. Böylece hangi işlemle hangi hareketin düzeltildiği korunur.
İade faturası da aynı şekilde satış hareketinin karşılığı olan bir giriş hareketi oluşturur. Ancak iade edilen ürünün tekrar satılabilir durumda olup olmadığı, farklı bir depoya alınacağı veya kalite kontrol bekleyeceği gibi işletme kuralları varsa doğrudan satılabilir stok bakiyesine eklenmemelidir. Bu durumda iade deposu veya bekleyen stok türü kullanılmalıdır.
İptal ve iade işlemlerinde de başlık, satır, ters hareket ve bakiye güncellemesi tek işlem içinde yapılmalıdır. Eski hareketi fiziksel olarak silmek, denetim geçmişini ve raporların dayanağını zayıflatır; yalnızca açık bir veri düzeltme politikası varsa ve yetkili işlem olarak uygulanıyorsa tercih edilmelidir.
Kayıt sonrasında nasıl kontrol edilir?
Kayıt başarılı olduğunda yalnızca ekranda “kaydedildi” mesajını görmek yeterli değildir. Aynı fatura kimliği üzerinden şu üç sonucu karşılaştırın:
- Fatura başlığı ve beklenen fatura satırı sayısı.
- Her fatura satırı için bir stok hareketi.
- Hareket miktarı kadar azalmış depo bakiyesi.
Aynı faturayı ikinci kez kaydetme ihtimaline karşı
FATURA_ID + STOK_ID + SATIR_NO gibi uygun bir benzersiz kısıt veya işlem kontrolü kullanılabilir. Böyle bir kısıt, uygulama hatasıyla aynı stok çıkışının iki kez yazılmasını engelleyen son güvenlik katmanıdır; ancak kullanıcıya gösterilecek hata mesajı, bunun mükerrer kayıt mı yoksa başka bir veritabanı sorunu mu olduğunu ayırmalıdır.Sık karşılaşılan hatalar ve düzeltme yolu
Fatura kaydoluyor ancak stok azalmıyor.
Kodda yalnızca fatura başlığı ve satırları kaydediliyor, stok hareketi veya bakiye güncellemesi çağrılmıyor olabilir. Aynı fatura kimliğiyle
STOK_HAREKET tablosunu sorgulayın. Hareket yoksa kayıt prosedürünü; hareket varsa ancak bakiye değişmemişse bakiye güncelleme sorgusunu ve etkilenen satır sayısını kontrol edin.Stok iki kez azalıyor.
Hem stok hareketine negatif miktar yazılıp hem de bakiye ayrıca azaltılıyorsa ve bakiye hareketlerden yeniden hesaplanıyorsa çift düşüm oluşabilir. Sistemde stok bakiyesinin doğrudan mı güncellendiğini, yoksa hareketlerden mi hesaplandığını kesinleştirin ve tek bir yöntemi uygulayın.
Stok yetersiz olduğu hâlde satış tamamlanıyor.
Ekran kontrolü yapılmış olsa bile işlem anında tekrar kontrol yapılmıyorsa eş zamanlı satışlar nedeniyle bu durum oluşabilir. Bakiye okuma, yeterlilik kontrolü ve güncelleme aynı işlem içinde yapılmalı; güncelleme beklenen mevcut bakiye koşuluyla gerçekleştirilmelidir. Sonuçta güncellenen satır sayısı beklenenden farklıysa işlem geri alınmalıdır.
Hata sonrası fatura başlığı veritabanında kalıyor.
Başlık, satır ve stok işlemleri farklı bağlantılarla veya farklı işlemler içinde çalıştırılıyorsa geri alma yalnızca son sorguyu etkileyebilir. Faturanın bütün kayıt işlemlerinde aynı bağlantı nesnesini ve aynı işlemi kullanın. Hata yakalandığında
Rollback çağrılmalı, ardından hata yeniden yükseltilerek kullanıcıya hangi stok kodu veya satır nedeniyle işlemin durduğu bildirilmelidir.Fatura kaydı tamamlandığında beklenen sonuç; her satırın faturaya, her stok hareketinin ilgili satıra ve her bakiye değişikliğinin doğru depoya bağlı olmasıdır. Bu ilişkiyi işlem bütünlüğü, birim dönüşümü, eş zamanlı stok kontrolü ve iptal-iade için ters hareket yaklaşımıyla birlikte kurarsanız satış faturası kaydı stok takibiyle tutarlı çalışır.