fix: preserve Turkish decimal prices and verify preparation persistence
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
|
||||
- status: Sunucuda kayıtlı taslak
|
||||
- alert: Kalemler ve teklif koşulları resmi dokümanla henüz karşılaştırılmadı. Kaynağı kontrol etmeden teklif çıktısı oluşturulamaz.
|
||||
- checkbox "Bu sürümün kalemlerini, miktarlarını, teklif türünü ve geçerlilik süresini resmi dokümanla karşılaştırdım."
|
||||
- text: Bu sürümün kalemlerini, miktarlarını, teklif türünü ve geçerlilik süresini resmi dokümanla karşılaştırdım.
|
||||
- heading "İstekli (firma) bilgileri" [level=5]
|
||||
- generic: Firma ticaret unvanı *
|
||||
- textbox "Firma ticaret unvanı *":
|
||||
- /placeholder: ""
|
||||
- text: QA Laboratuvar Test Ltd
|
||||
- generic: Tebligata esas adres *
|
||||
- textbox "Tebligata esas adres *":
|
||||
- /placeholder: ""
|
||||
- text: Yerel test adresi
|
||||
- generic: Vergi dairesi *
|
||||
- textbox "Vergi dairesi *":
|
||||
- /placeholder: ""
|
||||
- text: Test
|
||||
- generic: Vergi numarası *
|
||||
- textbox "Vergi numarası *":
|
||||
- /placeholder: ""
|
||||
- text: "0000000000"
|
||||
- generic: Ticaret sicil / MERSİS no
|
||||
- textbox "Ticaret sicil / MERSİS no":
|
||||
- /placeholder: ""
|
||||
- generic: Yetkili ad soyad *
|
||||
- textbox "Yetkili ad soyad *":
|
||||
- /placeholder: ""
|
||||
- text: Test Yetkilisi
|
||||
- generic: Yetkilinin görevi *
|
||||
- textbox "Yetkilinin görevi *":
|
||||
- /placeholder: Örn. Genel Müdür
|
||||
- text: Test Sorumlusu
|
||||
- generic: Telefon *
|
||||
- textbox "Telefon *":
|
||||
- /placeholder: ""
|
||||
- text: "0000000000"
|
||||
- generic: E-posta *
|
||||
- textbox "E-posta *":
|
||||
- /placeholder: ""
|
||||
- text: qa@example.test
|
||||
- generic: Teklif türü
|
||||
- combobox "Teklif türü":
|
||||
- option "Birim fiyat" [selected]
|
||||
- option "Anahtar teslim götürü bedel"
|
||||
- generic: Teklif geçerlilik (gün)
|
||||
- textbox "Teklif geçerlilik (gün)": "120"
|
||||
- generic: Kısım (varsa)
|
||||
- textbox "Kısım (varsa)":
|
||||
- /placeholder: Örn. 2. kısım
|
||||
- heading "Kalem fiyatlandırma" [level=5]
|
||||
- paragraph: Ondalık fiyatı 125,50 veya 125.50 biçiminde yazın; binlik ayırıcı kullanmayın.
|
||||
- table:
|
||||
- rowgroup:
|
||||
- row "Kalem Miktar Birim fiyat Tutar":
|
||||
- columnheader "Kalem"
|
||||
- columnheader "Miktar"
|
||||
- columnheader "Birim fiyat"
|
||||
- columnheader "Tutar"
|
||||
- rowgroup:
|
||||
- row "Sentetik test kalemi 2 adet 125,50 251,00":
|
||||
- cell "Sentetik test kalemi"
|
||||
- cell "2 adet"
|
||||
- cell "125,50":
|
||||
- textbox "0": 125,50
|
||||
- cell "251,00"
|
||||
- region "Belge ve ürün uygunluğu":
|
||||
- heading "Belge ve ürün uygunluğu" [level=5]
|
||||
- paragraph: Her kontrol için belge adı/sürümü ve kaynak maddeyi yazın. ÜTS, numune ve servis koşulları her ihalede zorunlu değildir; uygulanmıyorsa gerekçesini belirtin. Bu kayıtlar kullanıcı kontrolüdür; resmi uygunluk onayı değildir.
|
||||
- group "Sentetik yetki belgesi (zorunlu)":
|
||||
- generic: Sentetik yetki belgesi (zorunlu)
|
||||
- paragraph: Yerel QA dokümanı madde 7
|
||||
- button "Elle eklenen gereksinimi kaldır"
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı" [selected]
|
||||
- text: Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde
|
||||
- textbox "Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde": QA dosyası v1 — yalnız test, madde 7
|
||||
- paragraph: Resmi dokümandan eksik belge gereksinimi ekle
|
||||
- text: Belge adı
|
||||
- textbox "Belge adı"
|
||||
- text: Kaynak doküman ve madde
|
||||
- textbox "Kaynak doküman ve madde"
|
||||
- button "Belge gereksinimi ekle" [disabled]
|
||||
- generic "Sentetik test kalemi — ürün/hizmet ve yükümlülük kontrolü" [active]
|
||||
- text: Teklif edilen marka/model veya hizmet kapsamı
|
||||
- textbox "Teklif edilen marka/model veya hizmet kapsamı": QA Model 1
|
||||
- text: Teknik doküman/madde ve uygunluk dayanağı
|
||||
- textbox "Teknik doküman/madde ve uygunluk dayanağı": Sentetik QA teknik madde 3
|
||||
- text: Ürün/hizmet uygunluğu
|
||||
- combobox "Ürün/hizmet uygunluğu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Uygun" [selected]
|
||||
- option "Uygun değil"
|
||||
- group "ÜTS / ürün kaydı":
|
||||
- generic: ÜTS / ürün kaydı
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- text: Kaynak, karşılanan koşul veya uygulanmama gerekçesi
|
||||
- textbox "Kaynak, karşılanan koşul veya uygulanmama gerekçesi": Yalnız yerel sentetik test; gerçek şartname kontrolü değildir.
|
||||
- group "Numune / demonstrasyon":
|
||||
- generic: Numune / demonstrasyon
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- text: Kaynak, karşılanan koşul veya uygulanmama gerekçesi
|
||||
- textbox "Kaynak, karşılanan koşul veya uygulanmama gerekçesi": Yalnız yerel sentetik test; gerçek şartname kontrolü değildir.
|
||||
- group "Kurulum / bakım / servis":
|
||||
- generic: Kurulum / bakım / servis
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- text: Kaynak, karşılanan koşul veya uygulanmama gerekçesi
|
||||
- textbox "Kaynak, karşılanan koşul veya uygulanmama gerekçesi": Yalnız yerel sentetik test; gerçek şartname kontrolü değildir.
|
||||
- group "Teslimat ve süre":
|
||||
- generic: Teslimat ve süre
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- text: Kaynak, karşılanan koşul veya uygulanmama gerekçesi
|
||||
- textbox "Kaynak, karşılanan koşul veya uygulanmama gerekçesi": Yalnız yerel sentetik test; gerçek şartname kontrolü değildir.
|
||||
- text: Sorumlu
|
||||
- textbox "Sorumlu": QA Yetkilisi
|
||||
- generic: Teklif toplamı (KDV hariç)
|
||||
- generic: 251,00 TL
|
||||
- paragraph: "Yazı ile: ikiyüzellibir Türk Lirası"
|
||||
- heading "Hazırlanacak belgeler" [level=5]
|
||||
- button "Teklif Mektubu Rakam ve yazı ile bedel Yazdır / PDF" [disabled]:
|
||||
- generic: Teklif Mektubu
|
||||
- generic: Rakam ve yazı ile bedel
|
||||
- text: Yazdır / PDF
|
||||
- button "Birim Fiyat Teklif Cetveli 1 kalem Yazdır / PDF" [disabled]:
|
||||
- generic: Birim Fiyat Teklif Cetveli
|
||||
- generic: 1 kalem
|
||||
- text: Yazdır / PDF
|
||||
- button "Evrak Kontrol Listesi 1 belge Yazdır / PDF" [disabled]:
|
||||
- generic: Evrak Kontrol Listesi
|
||||
- generic: 1 belge
|
||||
- text: Yazdır / PDF
|
||||
- paragraph: Tutar, kaynak, belge ve ürün uygunluğu kontrollerini tamamlayın.
|
||||
- paragraph: Belgeler taslaktır ve idarenin standart formlarının yerine geçmez. İmzalamadan önce ihale dokümanındaki form metinleriyle karşılaştırın.
|
||||
- button
|
||||
- button "Kullanım kılavuzu (? tuşu)":
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
@@ -0,0 +1,122 @@
|
||||
|
||||
- status: Sunucuda kayıtlı taslak
|
||||
- alert: Kalemler ve teklif koşulları resmi dokümanla henüz karşılaştırılmadı. Kaynağı kontrol etmeden teklif çıktısı oluşturulamaz.
|
||||
- checkbox "Bu sürümün kalemlerini, miktarlarını, teklif türünü ve geçerlilik süresini resmi dokümanla karşılaştırdım."
|
||||
- text: Bu sürümün kalemlerini, miktarlarını, teklif türünü ve geçerlilik süresini resmi dokümanla karşılaştırdım.
|
||||
- heading "İstekli (firma) bilgileri" [level=5]
|
||||
- generic: Firma ticaret unvanı *
|
||||
- textbox "Firma ticaret unvanı *":
|
||||
- /placeholder: ""
|
||||
- text: QA Laboratuvar Test Ltd
|
||||
- generic: Tebligata esas adres *
|
||||
- textbox "Tebligata esas adres *":
|
||||
- /placeholder: ""
|
||||
- text: Yerel test adresi
|
||||
- generic: Vergi dairesi *
|
||||
- textbox "Vergi dairesi *":
|
||||
- /placeholder: ""
|
||||
- text: Test
|
||||
- generic: Vergi numarası *
|
||||
- textbox "Vergi numarası *":
|
||||
- /placeholder: ""
|
||||
- text: "0000000000"
|
||||
- generic: Ticaret sicil / MERSİS no
|
||||
- textbox "Ticaret sicil / MERSİS no":
|
||||
- /placeholder: ""
|
||||
- generic: Yetkili ad soyad *
|
||||
- textbox "Yetkili ad soyad *":
|
||||
- /placeholder: ""
|
||||
- text: Test Yetkilisi
|
||||
- generic: Yetkilinin görevi *
|
||||
- textbox "Yetkilinin görevi *":
|
||||
- /placeholder: Örn. Genel Müdür
|
||||
- text: Test Sorumlusu
|
||||
- generic: Telefon *
|
||||
- textbox "Telefon *":
|
||||
- /placeholder: ""
|
||||
- text: "0000000000"
|
||||
- generic: E-posta *
|
||||
- textbox "E-posta *":
|
||||
- /placeholder: ""
|
||||
- text: qa@example.test
|
||||
- generic: Teklif türü
|
||||
- combobox "Teklif türü":
|
||||
- option "Birim fiyat" [selected]
|
||||
- option "Anahtar teslim götürü bedel"
|
||||
- generic: Teklif geçerlilik (gün)
|
||||
- textbox "Teklif geçerlilik (gün)": "120"
|
||||
- generic: Kısım (varsa)
|
||||
- textbox "Kısım (varsa)":
|
||||
- /placeholder: Örn. 2. kısım
|
||||
- heading "Kalem fiyatlandırma" [level=5]
|
||||
- table:
|
||||
- rowgroup:
|
||||
- row "Kalem Miktar Birim fiyat Tutar":
|
||||
- columnheader "Kalem"
|
||||
- columnheader "Miktar"
|
||||
- columnheader "Birim fiyat"
|
||||
- columnheader "Tutar"
|
||||
- rowgroup:
|
||||
- row "Sentetik test kalemi 2 adet 125.50 251,00":
|
||||
- cell "Sentetik test kalemi"
|
||||
- cell "2 adet"
|
||||
- cell "125.50":
|
||||
- textbox "0": "125.50"
|
||||
- cell "251,00"
|
||||
- region "Belge ve ürün uygunluğu":
|
||||
- heading "Belge ve ürün uygunluğu" [level=5]
|
||||
- paragraph: Her kontrol için belge adı/sürümü ve kaynak maddeyi yazın. ÜTS, numune ve servis koşulları her ihalede zorunlu değildir; uygulanmıyorsa gerekçesini belirtin. Bu kayıtlar kullanıcı kontrolüdür; resmi uygunluk onayı değildir.
|
||||
- group "Sentetik yetki belgesi (zorunlu)":
|
||||
- generic: Sentetik yetki belgesi (zorunlu)
|
||||
- paragraph: Yerel QA dokümanı madde 7
|
||||
- button "Elle eklenen gereksinimi kaldır"
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli" [selected]
|
||||
- option "Doğrulandı"
|
||||
- text: Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde
|
||||
- textbox "Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde"
|
||||
- paragraph: Resmi dokümandan eksik belge gereksinimi ekle
|
||||
- text: Belge adı
|
||||
- textbox "Belge adı"
|
||||
- text: Kaynak doküman ve madde
|
||||
- textbox "Kaynak doküman ve madde"
|
||||
- button "Belge gereksinimi ekle" [disabled]
|
||||
- generic "Sentetik test kalemi — ürün/hizmet ve yükümlülük kontrolü"
|
||||
- option "Kontrol gerekli" [selected]
|
||||
- option "Uygun"
|
||||
- option "Uygun değil"
|
||||
- option "Kontrol gerekli" [selected]
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli"
|
||||
- option "Kontrol gerekli" [selected]
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli"
|
||||
- option "Kontrol gerekli" [selected]
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli"
|
||||
- option "Kontrol gerekli" [selected]
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli"
|
||||
- generic "6 belge/uygunluk kontrolü tamamlanmalı"
|
||||
- generic: Teklif toplamı (KDV hariç)
|
||||
- generic: 251,00 TL
|
||||
- paragraph: "Yazı ile: ikiyüzellibir Türk Lirası"
|
||||
- heading "Hazırlanacak belgeler" [level=5]
|
||||
- button "Teklif Mektubu Rakam ve yazı ile bedel Yazdır / PDF" [disabled]:
|
||||
- generic: Teklif Mektubu
|
||||
- generic: Rakam ve yazı ile bedel
|
||||
- text: Yazdır / PDF
|
||||
- button "Birim Fiyat Teklif Cetveli 1 kalem Yazdır / PDF" [disabled]:
|
||||
- generic: Birim Fiyat Teklif Cetveli
|
||||
- generic: 1 kalem
|
||||
- text: Yazdır / PDF
|
||||
- button "Evrak Kontrol Listesi 1 belge Yazdır / PDF" [disabled]:
|
||||
- generic: Evrak Kontrol Listesi
|
||||
- generic: 1 belge
|
||||
- text: Yazdır / PDF
|
||||
- paragraph: Tutar, kaynak, belge ve ürün uygunluğu kontrollerini tamamlayın.
|
||||
- paragraph: Belgeler taslaktır ve idarenin standart formlarının yerine geçmez. İmzalamadan önce ihale dokümanındaki form metinleriyle karşılaştırın.
|
||||
- button
|
||||
- button "Kullanım kılavuzu (? tuşu)":
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
@@ -0,0 +1,72 @@
|
||||
[level=5]
|
||||
- paragraph: Ondalık fiyatı 125,50 veya 125.50 biçiminde yazın; binlik ayırıcı kullanmayın.
|
||||
- table:
|
||||
- rowgroup:
|
||||
- row "Kalem Miktar Birim fiyat Tutar":
|
||||
- columnheader "Kalem"
|
||||
- columnheader "Miktar"
|
||||
- columnheader "Birim fiyat"
|
||||
- columnheader "Tutar"
|
||||
- rowgroup:
|
||||
- row "Sentetik test kalemi 2 adet 125,50 251,00":
|
||||
- cell "Sentetik test kalemi"
|
||||
- cell "2 adet"
|
||||
- cell "125,50":
|
||||
- textbox "0": 125,50
|
||||
- cell "251,00"
|
||||
- region "Belge ve ürün uygunluğu":
|
||||
- heading "Belge ve ürün uygunluğu" [level=5]
|
||||
- paragraph: Her kontrol için belge adı/sürümü ve kaynak maddeyi yazın. ÜTS, numune ve servis koşulları her ihalede zorunlu değildir; uygulanmıyorsa gerekçesini belirtin. Bu kayıtlar kullanıcı kontrolüdür; resmi uygunluk onayı değildir.
|
||||
- group "Sentetik yetki belgesi (zorunlu)":
|
||||
- generic: Sentetik yetki belgesi (zorunlu)
|
||||
- paragraph: Yerel QA dokümanı madde 7
|
||||
- button "Elle eklenen gereksinimi kaldır"
|
||||
- combobox "Kontrol durumu":
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı" [selected]
|
||||
- text: Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde
|
||||
- textbox "Belge adı/sürümü, geçerlilik kontrolü ve kaynak madde": QA dosyası v1 — yalnız test, madde 7
|
||||
- paragraph: Resmi dokümandan eksik belge gereksinimi ekle
|
||||
- text: Belge adı
|
||||
- textbox "Belge adı"
|
||||
- text: Kaynak doküman ve madde
|
||||
- textbox "Kaynak doküman ve madde"
|
||||
- button "Belge gereksinimi ekle" [disabled]
|
||||
- generic "Sentetik test kalemi — ürün/hizmet ve yükümlülük kontrolü"
|
||||
- option "Kontrol gerekli"
|
||||
- option "Uygun" [selected]
|
||||
- option "Uygun değil"
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- option "Kontrol gerekli"
|
||||
- option "Doğrulandı"
|
||||
- option "Uygulanmıyor — gerekçe gerekli" [selected]
|
||||
- generic: Teklif toplamı (KDV hariç)
|
||||
- generic: 251,00 TL
|
||||
- paragraph: "Yazı ile: ikiyüzellibir Türk Lirası"
|
||||
- heading "Hazırlanacak belgeler" [level=5]
|
||||
- button "Teklif Mektubu Rakam ve yazı ile bedel Yazdır / PDF" [disabled]:
|
||||
- generic: Teklif Mektubu
|
||||
- generic: Rakam ve yazı ile bedel
|
||||
- text: Yazdır / PDF
|
||||
- button "Birim Fiyat Teklif Cetveli 1 kalem Yazdır / PDF" [disabled]:
|
||||
- generic: Birim Fiyat Teklif Cetveli
|
||||
- generic: 1 kalem
|
||||
- text: Yazdır / PDF
|
||||
- button "Evrak Kontrol Listesi 1 belge Yazdır / PDF" [disabled]:
|
||||
- generic: Evrak Kontrol Listesi
|
||||
- generic: 1 belge
|
||||
- text: Yazdır / PDF
|
||||
- paragraph: Tutar, kaynak, belge ve ürün uygunluğu kontrollerini tamamlayın.
|
||||
- paragraph: Belgeler taslaktır ve idarenin standart formlarının yerine geçmez. İmzalamadan önce ihale dokümanındaki form metinleriyle karşılaştırın.
|
||||
- button
|
||||
- button "Kullanım kılavuzu (? tuşu)":
|
||||
- region "Notifications (F8)":
|
||||
- list
|
||||
@@ -63,9 +63,17 @@ Tarayıcı kanıtı: [Yenileme sonrası beş kart](evidence/qa-kanban-after-relo
|
||||
- Açık kalan Kanban, sağ panel ve takvim tarih etiketleri 30 saniyede bir güncellenir. Ürün aramasındaki geçmiş tarih filtresi aynı kaynak saatini kullanır; filtre etiketi bilinmeyen saati başvuruya açık garantisi gibi sunmaz.
|
||||
- Üç tarih testi gün farkını, Türkiye'de yıl/gece geçişini, tam kapanış anını, eksik saat ve artık günleri doğrular. Google/Outlook gerçek içe aktarımı, zeyilname güncellemesi ve bütün sunucu bildirim zamanlayıcıları henüz bu kapsamda doğrulanmadı.
|
||||
|
||||
## Tarayıcı doğrulaması ve virgüllü fiyat hatası
|
||||
|
||||
- Geçici normal hesapla Giresun adlı sentetik kayıtta firma alanları, 120 gün, fiyat ve manuel belge gereksinimi dolduruldu; kaydetme/yenileme sonrası geri geldi. Eksik kontroller varken çıktı düğmeleri kapalı kaldı.
|
||||
- Ürün modeli, teknik kaynak, belge doğrulaması, sorumlu ve dört sağlık yükümlülüğü kontrolü kaydedilip yenilemeden sonra ekrandan doğrulandı. Resmi kaynak onayı verilmedi; gerçek başvuru veya resmi uygunluk iddiası yok.
|
||||
- Tarayıcı testi yeni P1 kusuru ortaya çıkardı: `125,50` girdisi virgül silinerek `12550` oluyordu. Girdi artık değiştirilmeden ortak fiyat ayrıştırıcısına gider; nokta/virgül ondalığı desteklenir, geçersiz metin reddedilir. PDF HTML'sinde de aynı ayrıştırıcı kullanılır, birim fiyat hassasiyeti korunur.
|
||||
- Düzeltme sonrasında tarayıcıda `125,50 × 2 = 251,00 TL` görüldü ve yeniden yüklemede korundu. Bir otomatik test ekran hesabı/PDF HTML tutarlılığı, geçersiz sayı ve aşırı tutarı kapsar.
|
||||
- Kanıtlar: [taslak yenilemesi](evidence/qa-draft-after-reload.txt), [virgüllü fiyat](evidence/qa-turkish-price-fixed.txt), [kontrol yenilemesi](evidence/qa-checks-after-reload.txt).
|
||||
|
||||
## Doğrulama
|
||||
|
||||
`npm test`: 30/30 geçti (son yeniden çalıştırma: 6 Eylül 2026).
|
||||
`npm test`: 31/31 geçti (son yeniden çalıştırma: 6 Eylül 2026).
|
||||
`npm run build`: geçti; mevcut büyük paket/dinamik import uyarıları sürüyor.
|
||||
`node --check server/index.js`, `node --check server/user-store.mjs`, `git diff --check`: geçti.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { bidRowTotal, validateBidRows, roundMoney } from '../services/bidValidation';
|
||||
import { bidRowTotal, validateBidRows, roundMoney, parseBidPrice } from '../services/bidValidation';
|
||||
import { sourceRows, bidSourceSnapshot, restoreBidPrices } from '../services/bidSource';
|
||||
import { PreparationChecks } from './PreparationChecks';
|
||||
import { preparationBlockers, documentKey } from '../services/preparationChecks';
|
||||
@@ -91,7 +91,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {
|
||||
else setHata('Taslak kaydedilemedi. Tekrar deneyin.');
|
||||
};
|
||||
|
||||
const toplam = teklifTuru === 'goturu' ? (Number(goturuBedel) || 0) : kalemToplami;
|
||||
const toplam = teklifTuru === 'goturu' ? (parseBidPrice(goturuBedel) || 0) : kalemToplami;
|
||||
|
||||
const eksikler = ALANLAR.filter((a) => a.zorunlu && !String(firma[a.k] || '').trim());
|
||||
const kalemEksik = teklifTuru === 'birim' && validateBidRows(satirlar).length > 0;
|
||||
@@ -246,7 +246,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {
|
||||
</span>
|
||||
<input
|
||||
value={goturuBedel}
|
||||
onChange={(e) => setGoturuBedel(e.target.value.replace(/[^\d.]/g, ''))}
|
||||
onChange={(e) => setGoturuBedel(e.target.value)}
|
||||
placeholder="0"
|
||||
className="w-full sm:w-64 text-sm font-bold px-3 py-2 rounded-lg border border-slate-300"
|
||||
/>
|
||||
@@ -254,6 +254,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {
|
||||
) : kalemler.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<h5 className="font-extrabold text-xs text-slate-900">Kalem fiyatlandırma</h5>
|
||||
<p className="text-xs text-slate-500">Ondalık fiyatı 125,50 veya 125.50 biçiminde yazın; binlik ayırıcı kullanmayın.</p>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-xs min-w-[520px]">
|
||||
<thead>
|
||||
@@ -278,7 +279,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {
|
||||
<input
|
||||
value={fiyatlar[r.priceKey] ?? ''}
|
||||
onChange={(e) =>
|
||||
setFiyatlar((o) => ({ ...o, [r.priceKey]: e.target.value.replace(/[^\d.]/g, '') }))}
|
||||
setFiyatlar((o) => ({ ...o, [r.priceKey]: e.target.value }))}
|
||||
placeholder={r.estimatedUnitPrice != null ? String(r.estimatedUnitPrice) : '0'}
|
||||
className="w-28 text-xs text-right px-2 py-1 rounded border border-slate-300"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { quantityParts, bidRowTotal, validateBidRows } from './bidValidation.js';
|
||||
import { quantityParts, parseBidPrice, bidRowTotal, validateBidRows } from './bidValidation.js';
|
||||
/**
|
||||
* İhale dosyası belgeleri — teklif mektubu, birim fiyat teklif cetveli ve
|
||||
* evrak kontrol listesi.
|
||||
@@ -162,7 +162,7 @@ export function teklifCetveliHtml(f, satirlar, toplam) {
|
||||
if (errors.length) throw new Error(errors.join(" "));
|
||||
const govde = satirlar.map((r, i) => {
|
||||
const { miktar, birim } = miktarAyristir(r.quantity);
|
||||
const bf = Number(r.birimFiyat) || 0;
|
||||
const bf = parseBidPrice(r.birimFiyat);
|
||||
const tutar = bidRowTotal(r);
|
||||
return `<tr>
|
||||
<td class="s">${i + 1}</td>
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
// Preserve typed input; never delete a decimal separator and change its value.
|
||||
export function parseBidPrice(value) {
|
||||
if (typeof value === 'number') return Number.isFinite(value) && value >= 0 ? value : null;
|
||||
const text = String(value ?? '').trim();
|
||||
const plain = /^\d+(?:[.,]\d{1,8})?$/;
|
||||
const groupedTurkish = /^\d{1,3}(?:\.\d{3})+,\d{1,8}$/;
|
||||
if (!plain.test(text) && !groupedTurkish.test(text)) return null;
|
||||
const normalized = text.includes(',') ? text.replace(/\./g,'').replace(',','.') : text;
|
||||
const result = Number(normalized);
|
||||
return Number.isFinite(result) ? result : null;
|
||||
}
|
||||
export function quantityParts(value) {
|
||||
if (typeof value === 'number') return {miktar:Number.isFinite(value) ? value : null,birim:''};
|
||||
const match = /^\s*([+-]?[\d]+(?:[.,]\d+)*)\s+(.+?)\s*$/.exec(String(value || ''));
|
||||
@@ -11,8 +22,9 @@ export function quantityParts(value) {
|
||||
export const roundMoney = n => Math.round((n + Number.EPSILON) * 100) / 100;
|
||||
export function bidRowTotal(row) {
|
||||
const {miktar} = quantityParts(row.quantity);
|
||||
const price = Number(row.birimFiyat);
|
||||
return miktar > 0 && Number.isFinite(price) && price > 0 ? roundMoney(miktar * price) : null;
|
||||
const price = parseBidPrice(row.birimFiyat);
|
||||
const total = miktar * price;
|
||||
return miktar > 0 && price > 0 && Number.isFinite(total) && total <= Number.MAX_SAFE_INTEGER / 100 ? roundMoney(total) : null;
|
||||
}
|
||||
export function validateBidRows(rows) {
|
||||
if (!rows.length) return ['Resmi teklif cetvelindeki kalemler eksik.'];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {test} from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {quantityParts,validateBidRows,bidRowTotal} from '../src/services/bidValidation.js';
|
||||
import {quantityParts,validateBidRows,bidRowTotal,parseBidPrice} from '../src/services/bidValidation.js';
|
||||
import {yaziylaTL,teklifCetveliHtml} from '../src/services/bidDocs.js';
|
||||
test('Turkish quantities, decimal quantities and units are preserved',()=>{
|
||||
assert.deepEqual(quantityParts('2.138.330 puan'),{miktar:2138330,birim:'puan'});
|
||||
@@ -22,3 +22,15 @@ test('100 rows retain Turkish names and escaped content in printable sheet',()=>
|
||||
assert.equal((out.match(/Ölçüm <kit>/g)||[]).length,100);
|
||||
assert.ok(out.includes('İğdır'));assert.ok(out.includes('2.000,00'));
|
||||
});
|
||||
|
||||
test('Turkish decimal price is identical in row total and printable sheet',()=>{
|
||||
const row={name:'Kit',quantity:'2 adet',birimFiyat:'125,50'};
|
||||
assert.equal(parseBidPrice(row.birimFiyat),125.5);
|
||||
assert.equal(parseBidPrice('1.250,50'),1250.5);
|
||||
assert.equal(bidRowTotal(row),251);
|
||||
const html=teklifCetveliHtml({},[row],251);
|
||||
assert.ok(html.includes('125,50'));assert.ok(html.includes('251,00'));
|
||||
assert.ok(!html.includes('25.100'));
|
||||
for(const value of ['', '125,,50','1e3','125 TL','1.2.3','-20','125,']) assert.equal(parseBidPrice(value),null);
|
||||
assert.equal(bidRowTotal({quantity:'1000000000 adet',birimFiyat:'999999999999999'}),null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user