diff --git a/server/dt-store.mjs b/server/dt-store.mjs index 13bdd46..1f843b9 100644 --- a/server/dt-store.mjs +++ b/server/dt-store.mjs @@ -4,7 +4,7 @@ */ import fs from 'node:fs'; -import { sektorBul } from './siniflandir.mjs'; +import { sektorKarar } from './siniflandir.mjs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { titleCaseTr } from './tr-text.mjs'; @@ -28,13 +28,37 @@ function parseDetail(detailJson) { const dt = d.DogrudanTeminBilgileri || {}; const idare = d.IdareBilgileri || {}; const soz = d.SozlesmeBilgileri?.SozlesmeBilgisiList?.[0] || null; + const ilan = d.IlanBilgileri || {}; + const duyuru = ilan.DogrudanTeminIlanBilgisiList?.[0] || null; + const sonuc = ilan.SonucIlanBilgisiList?.[0] || null; + const iptal = ilan.IptalIlanBilgisiList?.[0] || null; return { yasaMaddesi: dt.YasaKapsamiTeminMaddesi || null, + istisnaDayanagi: dt.IstisnaAliminDayanagi || null, durum: dt.DtDurumu || null, + // BransKodList aslında OKAS (CPV) kodudur — idarenin kendi sınıflandırması. bransKodlari: dt.BransKodList || [], kismiTeklif: dt.KismiTeklif || null, + kisimSayisi: dt.KisimSayisi || null, + ilaninSekli: dt.IlaninSekli || null, + tarihSaat: dt.DtTarihSaati || null, + eIhale: Boolean(dt.EIhale), + duyuruYapilacak: Boolean(dt.DogrudanTeminDuyurusuYapilacakMi), + sozlesmeTasarisiVar: Boolean(dt.DogrudanTeminSozlesmeTasarisiVarMi), + iptalNedeni: dt.IptalNedeni || null, + iptalTarihi: dt.IptalTarihi || null, idareTam: idare.Idare || null, + ustIdare: idare.UstIdare || null, enUstIdare: idare.EnUstIdare || null, + ili: idare.Ili || null, + // İlan belgeleri — EncIlanId ile EKAP'ta görüntülenir + // EncIlanId → EKAP ilan görüntüleme adresi. Doğrulanmış tek yol + // EKAP arayüzü olduğundan bağlantıyı kullanıcının tarayıcısında açarız. + ilanlar: [ + duyuru && { tip: 'Doğrudan Temin Duyurusu', tarih: duyuru.IlanTarihi, encId: duyuru.EncIlanId }, + sonuc && { tip: 'Sonuç İlanı', tarih: sonuc.IlanTarihi, encId: sonuc.EncIlanId }, + iptal && { tip: 'İptal İlanı', tarih: iptal.IlanTarihi, encId: iptal.EncIlanId }, + ].filter(Boolean), kazananFirma: soz?.IstekliAdi || null, sozlesmeBedeli: soz?.SozlesmeBedeli || null, sozlesmeTarihi: soz?.SozlesmeTarihi || null, @@ -71,18 +95,32 @@ function mapDtRow(row) { sehir: row.ili && !row.ili.startsWith('Plaka') ? titleCaseTr(row.ili) : '', bolge: '', tarih: row.dt_tarihi || '', - saat: '', + saat: (det.tarihSaat || '').split(' ')[1] || '', alimTuru: 'Doğrudan Temin', - usul: det.yasaMaddesi ? `Doğrudan Temin (4734 ${det.yasaMaddesi})` : 'Doğrudan Temin (4734 22/d)', - // Sektör ihale TÜRÜ değildir; başlık + kalem adlarından çıkarılır. - sektor: sektorBul(row.isin_adi || '', (det.bransKodlari || []).join(' '), row.turu || ''), + usul: det.yasaMaddesi + ? `Doğrudan Temin (4734 ${det.yasaMaddesi})` + : det.istisnaDayanagi ? `Doğrudan Temin (istisna ${det.istisnaDayanagi})` : 'Doğrudan Temin', + // BransKodList = OKAS kodu → idarenin kendi sınıflandırması, başlıktan + // güvenilir. Kod yoksa iş adına düşülür. + sektor: sektorKarar( + (det.bransKodlari || []).map((k) => ({ kod: k })), + row.isin_adi || '', + ), brans: (det.bransKodlari || []).join(', ') || row.turu || 'Genel', kurumTipi: classifyKurum(kurum), eklenmeZamani: row.yayim_tarihi ? `Yayım: ${row.yayim_tarihi}` : '', isYeni: false, isLiveFeed: true, aciliyetBadge: null, - items: [], + // OKAS kalemleri — arayüzde kod+ad gösterilebilsin + items: (det.bransKodlari || []).map((k, i) => ({ + id: `okas-${i}`, kod: k, ad: `OKAS ${k}`, miktar: null, birim: null, birimFiyat: null, + })), + ilanBelgeleri: det.ilanlar || [], + ilaninSekli: det.ilaninSekli || null, + kismiTeklif: det.kismiTeklif || null, + eIhale: det.eIhale || false, + iptalNedeni: det.iptalNedeni || null, aciklama: null, yaklasikMaliyet: null, teslimatSuresi: null, diff --git a/server/dt-worker.mjs b/server/dt-worker.mjs index 9a09648..41861a6 100644 --- a/server/dt-worker.mjs +++ b/server/dt-worker.mjs @@ -36,7 +36,7 @@ const argVal = (name, def) => { return i >= 0 && args[i + 1] ? Number(args[i + 1]) : def; }; const MAX_PAGES = argVal('--pages', 10); -const MAX_DETAILS = argVal('--details', 40); +const MAX_DETAILS = argVal('--details', 250); const DELAY_BASE_MS = 2000; const DELAY_JITTER_MS = 1500; @@ -265,8 +265,12 @@ async function run() { // 2) Eksik detaylar const pending = db .prepare( + // EN YENİ kayıtlar önce: arayüz en yeni 5000'i gösteriyor; varsayılan + // (rowid) sırada en eskiler çekildiği için gösterilen hiçbir kayda + // detay ulaşmıyordu. `SELECT dogrudan_temin_id, idare_id FROM dt_tenders - WHERE detail_json IS NULL AND idare_id != '' LIMIT ?`, + WHERE detail_json IS NULL AND idare_id != '' + ORDER BY rowid DESC LIMIT ?`, ) .all(MAX_DETAILS); console.log(`[dt-worker] ${pending.length} detay çekilecek`); diff --git a/server/scheduler.mjs b/server/scheduler.mjs index 9fe933e..95ee388 100644 --- a/server/scheduler.mjs +++ b/server/scheduler.mjs @@ -101,7 +101,9 @@ function dtTara() { dtCalisiyor = true; dtSonTur = { baslangic: new Date().toISOString() }; const pages = process.env.DT_PAGES || '5'; - const details = process.env.DT_DETAILS || '30'; + // 30 detay/4 saat ile 10 bin kayıt 55 günde dolar; sektör sınıflandırması + // detaya bağlı olduğu için bütçe yükseltildi. + const details = process.env.DT_DETAILS || '250'; console.log(`[scheduler] doğrudan temin taraması başlıyor (${pages} sayfa, ${details} detay)`); const p = spawn(process.execPath, [path.join(__dirname, 'dt-worker.mjs'), '--pages', pages, '--details', details], { diff --git a/server/siniflandir.mjs b/server/siniflandir.mjs index 0bdc3f8..ac149b1 100644 --- a/server/siniflandir.mjs +++ b/server/siniflandir.mjs @@ -118,8 +118,26 @@ export function ilanKonusu(metin) { const OKAS_GRUP = { // 39 — mobilya / ev aletleri / temizlik ürünleri aynı bölümde '3971': 'Isıtma & İklimlendirme', // elektrikli ev aletleri (buzdolabı, klima) + '3922': 'Temizlik & Hijyen', // süpürge, fırça, temizlik gereçleri + '3953': 'Temizlik & Hijyen', // paspas, yer örtüsü '3983': 'Temizlik & Hijyen', // temizlik ürünleri '3984': 'Temizlik & Hijyen', + // 33 — tıbbi bölümde hijyenik kâğıt da var + '3376': 'Temizlik & Hijyen', // tuvalet kâğıdı, kâğıt havlu + '3377': 'Temizlik & Hijyen', + // 50 — bakım/onarım hizmetleri: taşıt onarımı ayrılır + '5011': 'Otomotiv & İş Makinesi', + '5012': 'Otomotiv & İş Makinesi', + '5013': 'Otomotiv & İş Makinesi', + '5014': 'Otomotiv & İş Makinesi', + // 03 — tarımsal ürünlerin yenilebilir olanları gıdadır + '0311': 'Gıda & Catering', + '0313': 'Gıda & Catering', + '0314': 'Gıda & Catering', + '0321': 'Gıda & Catering', + '0322': 'Gıda & Catering', + '0331': 'Gıda & Catering', + '0332': 'Gıda & Catering', // 79 — iş hizmetleri: güvenlik, matbaa, reklam, danışmanlık, personel '7971': 'Güvenlik Sistemleri', '7972': 'Güvenlik Sistemleri',