feat: urun arama — urun adini ihale DOKUMANLARININ icinde aratir

saidsurucu/ihale-mcp deposu incelenirken EKAP arama API'sinin doküman ici
arama parametreleri oldugu goruldu: teklifCetvelindeAra, teknikSartnamedeAra,
idariSartnamedeAra, sozlesmeTasarisindaAra. EKAP dokumani kendi tarafinda
indeksliyor — yani teklif cetvelinde arama icin dokumani indirmeye (ve
CAPTCHA'ya) gerek yok.

Kullanicinin sikayetini olcen kanit:
  ELDIVEN         basliкta  1.135  → teklif cetvelinde 29.057
  ORTOPEDIK VIDA  baslikta      0  → teklif cetvelinde      3
  HEMOGRAM        baslikta    283  → teklif cetvelinde  2.529
"ORTOPEDIK VIDA" hicbir ihale BASLIGINDA gecmiyor; gectigi ihalelerin adi
"VETERINER ILAC ... SARF MALZEME". Basliga bakan firma bunlari bulamiyordu.

- ekap-v2.mjs: searchBody artik opts.kapsam ile daraltilabiliyor;
  searchInDocuments() yalnizca teklif cetvelinde (istege bagli teknik
  sartnamede) arar ve sonucu normalize eder
- GET /api/urun-ara?q=&skip=&take=&teknik= — 5 dk bellek onbellegi ile
  (EKAP'a uzak cagri, ayni sorgu kaynagi yormasin)
- Header'da "URUN ARA" dugmesi + UrunAramaModal: sayfalama, "sadece teklif
  verilebilir" suzgeci, havuzda acik olan ihalede detayi acma kisayolu

SINIR: EKAP hangi kalemin eslestigini dondurmuyor, yalnizca eslesen ihaleleri.
Kalem dokumu icin dokuman yuklemeye dayali ortak havuz yolu gecerli kaliyor;
arayuzde bu acikca yaziliyor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 12:55:36 +03:00
co-authored by Claude Opus 5
parent 8826ff8e68
commit ff797f5a91
7 changed files with 309 additions and 1 deletions
+40
View File
@@ -89,10 +89,14 @@ function searchBody(opts = {}) {
return {
searchText: opts.searchText || '',
filterType: null,
// Arama kapsamı — EKAP metni ihale dokümanının İÇİNDE de arayabiliyor
// (teknik/idari şartname, sözleşme tasarısı, teklif cetveli). Varsayılan
// hepsi açık; opts.kapsam ile daraltılır (ör. yalnızca teklif cetveli).
ikNdeAra: true, ihaleAdindaAra: true, ihaleIlanindaAra: true,
teknikSartnamedeAra: true, idariSartnamedeAra: true, benzerIsMaddesindeAra: true,
isinYapilacagiYerMaddesindeAra: true, nitelikTurMiktarMaddesindeAra: true,
ihaleBilgilerindeAra: true, sozlesmeTasarisindaAra: true, teklifCetvelindeAra: true,
...(opts.kapsam || {}),
searchType: 'GirdigimGibi',
iknYili: opts.iknYili ?? null,
iknSayi: opts.iknSayi ?? null,
@@ -133,6 +137,42 @@ export async function searchTenders(opts = {}) {
return { list: data.list || [], totalCount: data.totalCount ?? 0 };
}
/**
* Ürün/kalem araması — metni YALNIZCA ihale dokümanının içinde arar.
*
* Firmalar başlığa bakarak kendi ürünlerini bulamıyor: "ORTOPEDİK VİDA"
* hiçbir ihale BAŞLIĞINDA geçmiyor, ama üç ihalenin TEKLİF CETVELİNDE var —
* ve o ihalelerin adı "VETERİNER İLAÇ ... SARF MALZEME". EKAP dokümanı
* kendi tarafında indekslediği için doküman indirmeye gerek yok.
*/
export async function searchInDocuments(metin, { teknikSartname = false, skip = 0, take = 20 } = {}) {
const kapsam = {
ikNdeAra: false, ihaleAdindaAra: false, ihaleIlanindaAra: false,
teknikSartnamedeAra: !!teknikSartname, idariSartnamedeAra: false,
benzerIsMaddesindeAra: false, isinYapilacagiYerMaddesindeAra: false,
nitelikTurMiktarMaddesindeAra: false, ihaleBilgilerindeAra: false,
sozlesmeTasarisindaAra: false, teklifCetvelindeAra: true,
};
const data = await ekapPost(
'/b_ihalearama/api/Ihale/GetListByParameters',
searchBody({ searchText: metin, kapsam, skip, take }),
);
return {
totalCount: data.totalCount ?? 0,
list: (data.list || []).map((t) => ({
ikn: t.ikn,
baslik: t.ihaleAdi,
kurum: t.idareAdi,
sehir: t.ihaleIlAdi,
tur: t.ihaleTipAciklama,
usul: t.ihaleUsulAciklama,
durum: t.ihaleDurumAciklama,
ihaleTarihSaat: t.ihaleTarihSaat,
dokumanSayisi: t.dokumanSayisi ?? 0,
})),
};
}
// NOT: dönen `id` per-response şifreli token — kalıcı değil, aynı oturumda kullan.
export async function getIlanlar(ihaleId) {
const data = await ekapPost('/b_ihalearama/api/Ilan/GetList', { ihaleId });