Files

161 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Ortak kalem havuzu (SQLite).
*
* NEDEN: Teklif cetveli kalemleri EKAP'ın API'sinde yok, yalnızca ihale
* dokümanının içinde — ve doküman sayfası CAPTCHA ile korunuyor, sunucudan
* otomatik indirilemiyor. Kullanıcı dokümanı kendi tarayıcısından indirip
* yükleyince kalemler buraya yazılıyor ve o ihale HERKESİN kartında
* kalemleriyle görünür oluyor. İlk yükleyen herkes için açıyor.
*
* Kalıcı anahtar İKN — EKAP'ın `id` alanı anlık token, saklanmaz.
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { normalizeTr } from './siniflandir.mjs';
let DatabaseSync = null;
try {
({ DatabaseSync } = await import('node:sqlite'));
} catch {
console.warn('[kalem-store] node:sqlite yok — kalem havuzu devre dışı');
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DATA_DIR = process.env.IHALEIST_SHARED_DATA_DIR || path.join(__dirname, 'data');
const DB_PATH = path.join(DATA_DIR, 'kalem.sqlite');
const SCHEMA = `
CREATE TABLE IF NOT EXISTS ihale_kalem (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ikn TEXT NOT NULL,
sira INTEGER,
ad TEXT NOT NULL,
ad_norm TEXT NOT NULL,
miktar REAL,
birim TEXT
);
CREATE INDEX IF NOT EXISTS idx_kalem_ikn ON ihale_kalem(ikn);
CREATE INDEX IF NOT EXISTS idx_kalem_norm ON ihale_kalem(ad_norm);
CREATE TABLE IF NOT EXISTS kalem_kaynak (
ikn TEXT PRIMARY KEY,
kalem_sayisi INTEGER NOT NULL DEFAULT 0,
dosya TEXT,
yontem TEXT,
ekleyen TEXT,
zaman TEXT
);
`;
let db = null;
export function available() {
return !!DatabaseSync;
}
function open() {
if (db) return db;
if (!DatabaseSync) throw new Error('Kalem deposu bu sunucuda kullanılamıyor.');
fs.mkdirSync(DATA_DIR, { recursive: true });
const candidate = new DatabaseSync(DB_PATH);
try {
candidate.exec('PRAGMA journal_mode = WAL;');
candidate.exec(SCHEMA);
db = candidate;
} catch (error) { candidate.close(); throw error; }
return db;
}
/**
* Bir ihalenin kalemlerini yazar (varsa eskisini değiştirir).
* Boş liste yazılmaz — hatalı bir ayrıştırma mevcut iyi veriyi silmesin.
*/
export function kalemYaz(ikn, kalemler, { dosya, yontem, ekleyen } = {}) {
const d = open();
if (!d || !ikn || !Array.isArray(kalemler) || kalemler.length === 0) return 0;
const sil = d.prepare('DELETE FROM ihale_kalem WHERE ikn = ?');
const ekle = d.prepare(
'INSERT INTO ihale_kalem (ikn, sira, ad, ad_norm, miktar, birim) VALUES (?, ?, ?, ?, ?, ?)',
);
const ozet = d.prepare(
`INSERT INTO kalem_kaynak (ikn, kalem_sayisi, dosya, yontem, ekleyen, zaman)
VALUES (?, ?, ?, ?, ?, ?)
ON CONFLICT(ikn) DO UPDATE SET
kalem_sayisi = excluded.kalem_sayisi, dosya = excluded.dosya,
yontem = excluded.yontem, ekleyen = excluded.ekleyen, zaman = excluded.zaman`,
);
d.exec('BEGIN');
try {
sil.run(ikn);
for (const k of kalemler) {
ekle.run(ikn, k.sira ?? null, String(k.ad).slice(0, 300), normalizeTr(k.ad).slice(0, 300),
k.miktar ?? null, k.birim ? String(k.birim).slice(0, 30) : null);
}
ozet.run(ikn, kalemler.length, dosya || null, yontem || null, ekleyen || null, new Date().toISOString());
d.exec('COMMIT');
} catch (e) {
d.exec('ROLLBACK');
throw e;
}
return kalemler.length;
}
export function kalemOku(ikn) {
const d = open();
if (!d || !ikn) return { kalemler: [], kaynak: null };
const kalemler = d.prepare(
'SELECT sira, ad, miktar, birim FROM ihale_kalem WHERE ikn = ? ORDER BY COALESCE(sira, 999999), id',
).all(ikn);
const kaynak = d.prepare('SELECT kalem_sayisi, dosya, yontem, zaman FROM kalem_kaynak WHERE ikn = ?').get(ikn) || null;
return { kalemler, kaynak };
}
/** Havuzdaki tüm ihalelerin kalem özeti: ikn → { sayi, ilkler[] } */
export function kalemOzetleri(limit = 20000) {
const d = open();
if (!d) return new Map();
const ozet = d.prepare('SELECT ikn, kalem_sayisi FROM kalem_kaynak LIMIT ?').all(limit);
const harita = new Map();
if (!ozet.length) return harita;
const ilk = d.prepare('SELECT ad FROM ihale_kalem WHERE ikn = ? ORDER BY COALESCE(sira, 999999), id LIMIT 6');
for (const o of ozet) {
harita.set(o.ikn, { sayi: o.kalem_sayisi, ilkler: ilk.all(o.ikn).map((r) => r.ad) });
}
return harita;
}
/**
* Kalem metninde arama. Firmanın kendi ürününü ("eldiven", "ortopedik vida")
* hangi ihalelerde geçtiğini bulması için.
*/
export function kalemAra(q, { limit = 400 } = {}) {
const d = open();
const terim = normalizeTr(q || '').trim();
if (!d || terim.length < 2) return [];
const satirlar = d.prepare(
`SELECT ikn, ad, miktar, birim FROM ihale_kalem
WHERE ad_norm LIKE ? ORDER BY ikn LIMIT ?`,
).all(`%${terim}%`, limit * 5);
const harita = new Map();
for (const s of satirlar) {
if (!harita.has(s.ikn)) harita.set(s.ikn, { ikn: s.ikn, eslesen: [] });
const e = harita.get(s.ikn);
if (e.eslesen.length < 5) e.eslesen.push({ ad: s.ad, miktar: s.miktar, birim: s.birim });
if (harita.size >= limit) break;
}
return [...harita.values()];
}
export function kalemSayilari() {
const d = open();
if (!d) return { ihale: 0, kalem: 0 };
return {
ihale: d.prepare('SELECT COUNT(*) n FROM kalem_kaynak').get().n,
kalem: d.prepare('SELECT COUNT(*) n FROM ihale_kalem').get().n,
};
}