From 2271883be4e2dd9d86be108a4e7356b72177cc93 Mon Sep 17 00:00:00 2001 From: ht Date: Sat, 5 Sep 2026 23:53:43 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20EKAP=20dok=C3=BCmanlar=C4=B1=20a=C3=A7?= =?UTF-8?q?=C4=B1lm=C4=B1yor=20=E2=80=94=20yeni=20sekme=20t=C4=B1klama=20a?= =?UTF-8?q?n=C4=B1nda=20a=C3=A7=C4=B1l=C4=B1yor,=20bo=C5=9Fluklu=20=C4=B0K?= =?UTF-8?q?N'ler=20d=C3=BCzeltildi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit İki ayrı neden vardı: 1) window.open sunucu yanıtından SONRA çağrılıyordu; Safari, PWA ve sıkı ayarlı Chrome bunu açılır-pencere sayıp sessizce engelliyordu — düğme "çalışmıyor" görünüyordu. Sekme artık tıklamanın hemen içinde boş açılır, adres gelince yönlendirilir. Engellenirse gerçek bir bağlantı (a href) gösterilir; bağlantı tıklaması hiçbir tarayıcıda engellenmez. 2) ilan.gov.tr bazı kayıtlarda İKN'yi başta/sonda boşluk ve kesme boşluğu (nbsp) ile veriyor (" 2026/1622878"). Bu 50 kayıtta panel "EKAP formatında değil" deyip düğmeyi hiç göstermiyordu. İKN artık alınırken boşluksuz saklanıyor; panel ve doküman araması da savunma amaçlı temizliyor. Ayrıca: "EKAP'ta 0 doküman kayıtlı" yanıltıcı metni kaldırıldı (EKAP arama kaydı sayıyı vermiyor), EKAP'ın önce güvenlik doğrulaması istediği açıklandı. Co-Authored-By: Claude Fable 5.1 --- server/documents.mjs | 2 +- server/index.js | 5 +++- src/components/TenderDocuments.jsx | 46 ++++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/server/documents.mjs b/server/documents.mjs index ab948df..baabe70 100644 --- a/server/documents.mjs +++ b/server/documents.mjs @@ -16,7 +16,7 @@ const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 ( /** IKN'den taze bir EKAP kaydı bulur (id token anlıktır, saklanamaz). */ export async function findByIkn(ikn) { - const [yil, sayi] = String(ikn || '').split('/'); + const [yil, sayi] = String(ikn || '').replace(/[\s\u00a0]+/g, '').split('/'); if (!yil || !sayi) throw new Error('Geçersiz İKN'); const r = await searchTenders({ iknYili: Number(yil), iknSayi: Number(sayi), take: 1 }); const t = r.list[0]; diff --git a/server/index.js b/server/index.js index f8d9171..816ddb7 100644 --- a/server/index.js +++ b/server/index.js @@ -162,7 +162,10 @@ function mapAd(ad) { const cityUpper = (ad.addressCityName || '').toLocaleUpperCase('tr-TR'); const sehir = titleCaseTr(ad.addressCityName || ''); const tarih = getFilter(ad, 'İhale ve Teklif Açma Tarihi') || ''; - const ikn = getFilter(ad, 'İhale Kayıt No') || ad.adNo || ''; + // İKN'yi boşluksuz sakla: ilan.gov.tr bazı kayıtlarda başa/sona boşluk ve + // kesme boşluğu (nbsp) koyuyor; " 2026/1622878" EKAP formatı sayılmıyor ve + // doküman/analiz düğmeleri kapanıyordu (havuzda 50 kayıt). + const ikn = String(getFilter(ad, 'İhale Kayıt No') || ad.adNo || '').replace(/[\s\u00a0]+/g, ''); const usul = getFilter(ad, 'İhale Usulü') || 'Belirtilmemiş'; const ihaleTuru = getFilter(ad, 'İhale Türü') || ''; const searchBase = `${ad.title || ''} ${ad.advertiserName || ''}`; // yalnızca arama için diff --git a/src/components/TenderDocuments.jsx b/src/components/TenderDocuments.jsx index 4a0927a..4303549 100644 --- a/src/components/TenderDocuments.jsx +++ b/src/components/TenderDocuments.jsx @@ -16,8 +16,13 @@ export const TenderDocuments = ({ tender }) => { const [error, setError] = useState(null); const [bilgi, setBilgi] = useState(null); const [ilerleme, setIlerleme] = useState(''); + // Son üretilen EKAP doküman adresi — yeni sekme engellenirse kullanıcı + // gerçek bir bağlantıya tıklayarak açabilsin (bağlantı tıklaması engellenmez). + const [ekapUrl, setEkapUrl] = useState(null); - const ikn = tender?.ikn; + // Eski kayıtlarda İKN boşluklu gelebiliyor (" 2026/1622878"); EKAP tespiti + // ve sunucu çağrıları boşluksuz değerle yapılır. + const ikn = String(tender?.ikn || '').replace(/[\s\u00a0]+/g, '') || undefined; const [belgeler, setBelgeler] = useState([]); const [yukleniyor, setYukleniyor] = useState(false); const dosyaRef = useRef(null); @@ -106,13 +111,28 @@ export const TenderDocuments = ({ tender }) => { const dokumaniAc = async () => { setBusy('url'); setError(null); + // Açılır pencere engelleyici: yeni sekme, tıklamanın HEMEN içinde + // (await'ten önce) açılmalı. Sunucu yanıtından sonra çağrılan window.open + // Safari, PWA ve sıkı ayarlı Chrome'da sessizce engelleniyor — kullanıcı + // "düğme çalışmıyor / dokümanlar açılmıyor" görüyordu. Sekme boş açılır, + // adres gelince yönlendirilir; engellenirse aşağıda gerçek bağlantı çıkar. + let sekme = null; + try { sekme = window.open('', '_blank'); } catch { sekme = null; } + if (sekme) { + try { + sekme.opener = null; + sekme.document.write('

EKAP doküman sayfasına yönlendiriliyor…

'); + } catch { /* yazılamazsa da yönlendirme çalışır */ } + } try { const r = await fetch(`/api/documents/url?ikn=${encodeURIComponent(ikn)}`); const d = await r.json(); if (!d.success) throw new Error(d.error); - setBilgi({ dokumanSayisi: d.dokumanSayisi }); - window.open(d.url, '_blank', 'noopener,noreferrer'); + setEkapUrl(d.url); + setBilgi({ dokumanSayisi: d.dokumanSayisi, ekapSekme: sekme ? 'acildi' : 'engellendi' }); + if (sekme) sekme.location.href = d.url; } catch (e) { + if (sekme) { try { sekme.close(); } catch { /* yoksay */ } } setError(e.message); } finally { setBusy(null); } }; @@ -217,7 +237,8 @@ export const TenderDocuments = ({ tender }) => { <>

İhale dokümanları EKAP üzerinde tutulur. Aşağıdaki bağlantı sizi doğrudan bu ihalenin - EKAP doküman sayfasına götürür; ilan metnini ise analiz için buradan çekebilirsiniz. + EKAP doküman sayfasına götürür; EKAP önce kısa bir güvenlik doğrulaması (resim seçme) + ister, ardından şartname ve ekleri listelenir. İlan metnini ise analiz için buradan çekebilirsiniz.

@@ -240,9 +261,24 @@ export const TenderDocuments = ({ tender }) => {
+ {ekapUrl && ( +
+ +

+ {bilgi?.ekapSekme === 'engellendi' + ? <>Tarayıcınız yeni sekmeyi engelledi. + : <>EKAP doküman sayfası yeni sekmede açıldı. Açılmadıysa: } + + EKAP doküman sayfasını aç + + {' '}· Bağlantı bir süre sonra geçerliliğini yitirebilir; açılmazsa düğmeye yeniden basın. +

+
+ )} + {bilgi && !bilgi.derin && (

- {bilgi.dokumanSayisi != null && <>EKAP'ta {bilgi.dokumanSayisi} doküman kayıtlı. } + {bilgi.dokumanSayisi > 0 && <>EKAP'ta {bilgi.dokumanSayisi} doküman kayıtlı. } {bilgi.karakter != null && <>{bilgi.ilanSayisi} ilan · {bilgi.okasSayisi} kalem · {bilgi.karakter.toLocaleString('tr-TR')} karakter metin analize gönderildi. } {bilgi.yuklendi && ( <>