feat: canlı veri — ilan.gov.tr açık ihale + EKAP doğrudan temin (22/d)
- server/index.js: ilan.gov.tr proxy (açık ihale + kira/satış), TLS zincir düzeltmesi - server/dt-worker.mjs: EKAP doğrudan temin toplayıcı (Playwright, imzasız oturum içi fetch) - server/dt-store.mjs + il-plaka.mjs: DT verisini tender modeline çevirme - src/services/tenderApi.js + AppContext: mock veri yerine canlı API - Doğrudan Temin sekmesi 256 gerçek 22/d kaydıyla dolu
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* DT worker'ının doldurduğu SQLite'ı okuyup ihaleist tender modeline çevirir.
|
||||
* Veritabanı yoksa (worker hiç koşmadıysa) sessizce boş liste döner.
|
||||
*/
|
||||
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DB_PATH = path.join(__dirname, 'data', 'dt.sqlite');
|
||||
|
||||
function parseDetail(detailJson) {
|
||||
if (!detailJson) return {};
|
||||
try {
|
||||
const d = JSON.parse(detailJson).dogrudanTeminDetayResult || {};
|
||||
const dt = d.DogrudanTeminBilgileri || {};
|
||||
const idare = d.IdareBilgileri || {};
|
||||
const soz = d.SozlesmeBilgileri?.SozlesmeBilgisiList?.[0] || null;
|
||||
return {
|
||||
yasaMaddesi: dt.YasaKapsamiTeminMaddesi || null,
|
||||
durum: dt.DtDurumu || null,
|
||||
bransKodlari: dt.BransKodList || [],
|
||||
kismiTeklif: dt.KismiTeklif || null,
|
||||
idareTam: idare.Idare || null,
|
||||
enUstIdare: idare.EnUstIdare || null,
|
||||
kazananFirma: soz?.IstekliAdi || null,
|
||||
sozlesmeBedeli: soz?.SozlesmeBedeli || null,
|
||||
sozlesmeTarihi: soz?.SozlesmeTarihi || null,
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const KURUM_RULES = [
|
||||
[/hastane|sağlık|başhekim|tıp merkezi|adsm|ağız ve diş/i, 'Sağlık Kurumları'],
|
||||
[/ceza infaz|cezaevi|tutukevi|işyurt/i, 'Ceza İnfaz Kurumları'],
|
||||
[/üniversite|rektörlü/i, 'Üniversiteler'],
|
||||
[/büyükşehir belediye/i, 'Büyükşehir Belediyeleri'],
|
||||
[/belediye/i, 'Belediyeler'],
|
||||
[/bakanlığı|valiliği|müdürlüğü|komutanlığı/i, 'Bakanlık / Kamu Kurumları'],
|
||||
];
|
||||
|
||||
function classifyKurum(text) {
|
||||
for (const [re, label] of KURUM_RULES) if (re.test(text || '')) return label;
|
||||
return 'Diğer Kamu Kurumları';
|
||||
}
|
||||
|
||||
function mapDtRow(row) {
|
||||
const det = parseDetail(row.detail_json);
|
||||
const kurum = det.idareTam || row.idare_adi || '';
|
||||
return {
|
||||
id: `dt-${row.dogrudan_temin_id}`,
|
||||
kaynak: 'EKAP · Doğrudan Temin',
|
||||
kaynakUrl: 'https://ekap.kik.gov.tr/EKAP/YeniIhaleArama.aspx',
|
||||
ikn: row.dtn || '',
|
||||
baslik: row.isin_adi || '',
|
||||
kurum,
|
||||
sehir: row.ili && !row.ili.startsWith('Plaka') ? row.ili : '',
|
||||
bolge: '',
|
||||
tarih: row.dt_tarihi || '',
|
||||
saat: '',
|
||||
alimTuru: 'Doğrudan Temin',
|
||||
usul: det.yasaMaddesi ? `Doğrudan Temin (4734 ${det.yasaMaddesi})` : 'Doğrudan Temin (4734 22/d)',
|
||||
sektor: row.turu || 'Genel',
|
||||
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: [],
|
||||
aciklama: null,
|
||||
yaklasikMaliyet: null,
|
||||
teslimatSuresi: null,
|
||||
teslimYeri: null,
|
||||
yeterlikKriterleri: [],
|
||||
aiMatchScore: null,
|
||||
aiRiskScore: null,
|
||||
aiBidRange: null,
|
||||
kanbanStatus: 'incelenecek',
|
||||
atananPersonel: null,
|
||||
durum: det.durum || 'Aktif',
|
||||
incelendi: false,
|
||||
takipte: false,
|
||||
// Sonuçlanmış DT'lerde kazanan/fiyat bilgisi
|
||||
sozlesmeBedeli: det.sozlesmeBedeli || null,
|
||||
kazananFirma: det.kazananFirma || null,
|
||||
};
|
||||
}
|
||||
|
||||
export function readDtTenders(limit = 300) {
|
||||
if (!fs.existsSync(DB_PATH)) return { tenders: [], totalCount: 0, available: false };
|
||||
const db = new DatabaseSync(DB_PATH, { readOnly: true });
|
||||
try {
|
||||
const total = db.prepare('SELECT COUNT(*) c FROM dt_tenders').get().c;
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT dogrudan_temin_id, dtn, isin_adi, idare_adi, turu, ili, dt_tarihi, yayim_tarihi, detail_json
|
||||
FROM dt_tenders ORDER BY rowid DESC LIMIT ?`,
|
||||
)
|
||||
.all(limit);
|
||||
return { tenders: rows.map(mapDtRow), totalCount: total, available: true };
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user