100 lines
5.5 KiB
JavaScript
100 lines
5.5 KiB
JavaScript
// Haftalık brief — Pazartesi 08:00: takipteki ihalelerden bu hafta yapılacaklar,
|
||
// geçen hafta sonuçlanan takipliler, takiplilere gelen zeyilname sayısı.
|
||
// Telegram (bağlıysa) + e-posta. Son gönderim data/brief-son.json'da tutulur;
|
||
// saatlik kontrol, haftada bir gönderim (node-cron yok, setInterval).
|
||
import fs from 'node:fs';
|
||
import { createBriefSender } from './brief-delivery.mjs';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
import * as store from './user-store.mjs';
|
||
import { resultsByIkn } from './history-store.mjs';
|
||
import { emailConfigured, sendEmail } from './email.mjs';
|
||
import { sendTelegram, telegramConfigured } from './telegram.mjs';
|
||
import { yaklasanIhaleler, trTarih } from './feeds-mcp.mjs';
|
||
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
const DURUM_DOSYASI = path.join(__dirname, 'data', 'brief-son.json');
|
||
|
||
function sonGonderim() {
|
||
try { return JSON.parse(fs.readFileSync(DURUM_DOSYASI, 'utf8')); } catch { return {}; }
|
||
}
|
||
function sonGonderimYaz(d) {
|
||
try { fs.mkdirSync(path.dirname(DURUM_DOSYASI), { recursive: true }); fs.writeFileSync(DURUM_DOSYASI, JSON.stringify(d)); } catch { /* yok */ }
|
||
}
|
||
|
||
const paraTR = (v, birim) => (v == null ? '' : `${Number(v).toLocaleString('tr-TR')} ${birim || 'TL'}`);
|
||
|
||
/** Bir kullanıcı için brief içeriği. */
|
||
export function briefOlustur(email, tenders) {
|
||
const takip = store.getTrackedTenderKeys(email);
|
||
const yaklasan = yaklasanIhaleler(email, tenders, 7);
|
||
const birHafta = Date.now() - 7 * 86400000;
|
||
const sonuclar = [];
|
||
for (const ikn of takip.slice(0, 200)) {
|
||
const r = resultsByIkn(ikn) || [];
|
||
for (const s of r) {
|
||
const d = trTarih(s.sozlesmeTarihi) || trTarih(s.tarih);
|
||
if (d && d.getTime() >= birHafta) sonuclar.push({ ikn, baslik: s.baslik, kazanan: s.kazananFirma, bedel: s.sozlesmeBedeli, birim: s.paraBirimi, teklif: s.teklifSayisi });
|
||
}
|
||
}
|
||
const hafta = new Date().toLocaleDateString('tr-TR', { day: '2-digit', month: 'long', year: 'numeric' });
|
||
return { hafta, yaklasan, sonuclar: sonuclar.slice(0, 15), bos: !yaklasan.length && !sonuclar.length };
|
||
}
|
||
|
||
const deadlineText = t => t.son_teklif_zamani
|
||
? new Date(t.son_teklif_zamani).toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' }) + ' (Türkiye saati)'
|
||
: 'saat doğrulanmalı';
|
||
|
||
export function briefMetni(b) {
|
||
const s = [`📋 Haftalık İhale Brief — ${b.hafta}`];
|
||
if (b.yaklasan.length) {
|
||
s.push('', `⏰ Bu hafta yapılacak takipli ihaleler (${b.yaklasan.length}):`);
|
||
for (const t of b.yaklasan) s.push(`• ${t.kalan_gun === 0 ? 'BUGÜN' : t.kalan_gun + ' gün'} — ${t.baslik} (${t.kurum}, ${t.tarih} · ${deadlineText(t)})`);
|
||
}
|
||
if (b.sonuclar.length) {
|
||
s.push('', `🏁 Geçen hafta sonuçlanan takipliler (${b.sonuclar.length}):`);
|
||
for (const r of b.sonuclar) s.push(`• ${r.baslik} → ${r.kazanan || 'kazanan açıklanmadı'}${r.bedel != null ? ' · ' + paraTR(r.bedel, r.birim) : ''}${r.teklif != null ? ' · ' + r.teklif + ' teklif' : ''}`);
|
||
}
|
||
s.push('', 'ihaleist.com');
|
||
return s.join('\n');
|
||
}
|
||
|
||
export function briefHtml(b) {
|
||
const esc = (x) => String(x ?? '').replace(/[<>&]/g, (c) => ({ '<': '<', '>': '>', '&': '&' }[c]));
|
||
const bolum = (baslik, satirlar) => satirlar.length
|
||
? `<h3 style="margin:18px 0 6px;font-size:15px;color:#0f172a">${baslik}</h3><ul style="margin:0;padding-left:18px;color:#334155;font-size:14px">${satirlar.map((x) => `<li style="margin:4px 0">${x}</li>`).join('')}</ul>` : '';
|
||
return `<div style="font-family:Inter,Arial,sans-serif;max-width:640px;margin:auto;padding:20px;color:#0f172a">
|
||
<h2 style="margin:0 0 4px;font-size:20px">📋 Haftalık İhale Brief</h2><div style="color:#64748b;font-size:13px">${esc(b.hafta)}</div>
|
||
${bolum('⏰ Bu hafta yapılacak takipli ihaleler', b.yaklasan.map((t) => `<b>${t.kalan_gun === 0 ? 'BUGÜN' : t.kalan_gun + ' gün'}</b> — ${esc(t.baslik)} (${esc(t.kurum)}, ${esc(t.tarih)} · ${esc(deadlineText(t))})`))}
|
||
${bolum('🏁 Geçen hafta sonuçlanan takipliler', b.sonuclar.map((r) => `${esc(r.baslik)} → ${esc(r.kazanan || 'kazanan açıklanmadı')}${r.bedel != null ? ' · ' + esc(paraTR(r.bedel, r.birim)) : ''}`))}
|
||
<p style="margin-top:20px;font-size:12px;color:#94a3b8">Bu brief her Pazartesi gönderilir; Bildirimler sekmesinden kapatabilirsiniz.</p></div>`;
|
||
}
|
||
|
||
export const haftalikBriefGonder = createBriefSender({
|
||
store, buildBrief:briefOlustur, text:briefMetni, html:briefHtml,
|
||
telegramConfigured, sendTelegram, emailConfigured, sendEmail,
|
||
});
|
||
|
||
export function briefScheduleKey(now = new Date()) {
|
||
const tr = new Date(now.getTime() + 3 * 3600000);
|
||
if (tr.getUTCDay() !== 1 || tr.getUTCHours() < 8) return null;
|
||
return `${tr.getUTCFullYear()}-${tr.getUTCMonth()}-${tr.getUTCDate()}`;
|
||
}
|
||
|
||
/** Saatlik kontrol: Türkiye saatiyle Pazartesi ≥08:00. */
|
||
export function briefZamanlayici(tenderGetir) {
|
||
if (process.env.BRIEF_ENABLED === '0') { console.log('[brief] haftalık brief devre dışı (BRIEF_ENABLED=0)'); return; }
|
||
const kontrol = async () => {
|
||
const anahtar = briefScheduleKey();
|
||
if (!anahtar) return;
|
||
const d = sonGonderim();
|
||
if (d.son === anahtar) return;
|
||
sonGonderimYaz({ son: anahtar });
|
||
const r = await haftalikBriefGonder(tenderGetir).catch((e) => ({ hata: e.message }));
|
||
console.log('[brief] haftalık brief:', JSON.stringify(r));
|
||
};
|
||
setTimeout(kontrol, 3 * 60 * 1000).unref?.();
|
||
setInterval(kontrol, 60 * 60 * 1000).unref?.();
|
||
console.log('[brief] haftalık brief zamanlandı (Pazartesi 08:00, saatlik kontrol)');
|
||
}
|