- /mcp (JSON-RPC, ih_live_ anahtarı): search_tenders, get_tender, search_results, search_items, upcoming_deadlines - /api/feeds/tenders.rss (anahtarlı) ve /api/feeds/sonuc.rss (açık) - Haftalık brief (Pazartesi 08:00, Telegram + e-posta), /api/me/brief tercihi + önizleme, sağ panelde yaklaşan ihale tarihleri - Detayda Teklif Asistanı sekmesi: belgeye sor (atıflı), taslak denetimi, taslak üretimi (analyzeSpec zinciri) - Derin analiz belge listesi × Doküman Deposu eşleştirme rozeti - Kırmızı bayrak: kısa süre, hafta sonu tarih, geç sözleşme, kazananı gizli, DT parçalama şüphesi - Kullanım kılavuzu güncellendi Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
63 lines
3.2 KiB
React
63 lines
3.2 KiB
React
import React, { useEffect, useState } from 'react';
|
||
import { listDocuments } from '../services/account';
|
||
|
||
/**
|
||
* Derin analiz belge kontrol listesi × Doküman Deposu eşleştirmesi.
|
||
* Belge adı/kategori sözcüklerine göre depodaki evrakı bulur: Hazır /
|
||
* Yakında dolacak (≤30 gün) / Süresi dolmuş / Eksik. Depo listesi bir kez
|
||
* çekilip modül düzeyinde paylaşılır.
|
||
*/
|
||
const KATEGORI_DESEN = {
|
||
'Yasal Belgeler': /imza|sirk|vekalet|yetki|sicil|gazete|oda\s*kay|faaliyet|ticaret|kuruluş|adli|sabıka|iflas|konkordato/i,
|
||
'Kalite Belgeleri': /iso|kalite|tse|ce\b|sertifika|belgelendirme|9001|14001|45001|27001|hizmet\s*yeterlilik/i,
|
||
'Mali Belgeler': /bilanço|mali|gelir|ciro|banka|referans|teminat|vergi\s*borc|sgk|prim|sosyal\s*güvenlik/i,
|
||
'Resmi Kayıtlar': /vergi\s*levha|kimlik|ekap\s*kay|kayıt\s*belge|mersis|ticaret\s*sicil\s*no/i,
|
||
'Teknik Belgeler': /teknik|katalog|test\s*rapor|numune|uygunluk|garanti|referans\s*iş|iş\s*deneyim|iş\s*bitirme|kapasite|üretim/i,
|
||
};
|
||
|
||
let depoCache = null;
|
||
let depoPromise = null;
|
||
function depoyuGetir() {
|
||
if (depoCache) return Promise.resolve(depoCache);
|
||
if (!depoPromise) depoPromise = listDocuments().then((d) => (depoCache = d.belgeler || d.documents || [])).catch(() => (depoCache = []));
|
||
return depoPromise;
|
||
}
|
||
|
||
const norm = (s) => String(s ?? '').toLocaleLowerCase('tr-TR').replace(/[^\p{L}\p{N}\s]/gu, ' ').replace(/\s+/g, ' ').trim();
|
||
function benzerlik(a, b) {
|
||
const x = norm(a).split(' ').filter((k) => k.length >= 4);
|
||
if (!x.length) return 0;
|
||
const y = norm(b);
|
||
return x.filter((k) => y.includes(k)).length / x.length;
|
||
}
|
||
|
||
export function eslestir(belge, depo, bugun = new Date()) {
|
||
const kat = Object.entries(KATEGORI_DESEN).find(([, re]) => re.test(norm(belge)))?.[0] || null;
|
||
const aday = depo
|
||
.map((d) => ({ d, puan: (kat && d.kategori === kat ? 1 : 0) + benzerlik(belge, d.ad) }))
|
||
.filter((x) => x.puan >= 0.5)
|
||
.sort((a, b) => b.puan - a.puan)[0]?.d;
|
||
if (!aday) return { durum: 'eksik', kategori: kat, belge: null, kalan: null };
|
||
const son = aday.sonKullanma ? new Date(aday.sonKullanma) : null;
|
||
const kalan = son && !Number.isNaN(son.getTime()) ? Math.floor((son - bugun) / 86400000) : null;
|
||
const durum = kalan != null && kalan < 0 ? 'suresi_dolmus' : kalan != null && kalan <= 30 ? 'yakinda' : 'hazir';
|
||
return { durum, kategori: kat, belge: aday, kalan };
|
||
}
|
||
|
||
const GORUNUM = {
|
||
hazir: ['Depoda hazır', 'bg-emerald-100 text-emerald-800'],
|
||
yakinda: ['Yakında dolacak', 'bg-amber-100 text-amber-800'],
|
||
suresi_dolmus: ['Süresi dolmuş', 'bg-red-100 text-red-800'],
|
||
eksik: ['Depoda yok', 'bg-slate-200 text-slate-700'],
|
||
};
|
||
|
||
export const YeterlilikRozeti = ({ belge }) => {
|
||
const [depo, setDepo] = useState(depoCache);
|
||
useEffect(() => { if (!depo) depoyuGetir().then(setDepo); }, [depo]);
|
||
if (!depo || !belge) return null;
|
||
const e = eslestir(belge, depo);
|
||
const [etiket, sinif] = GORUNUM[e.durum];
|
||
const ipucu = e.belge ? `${e.belge.ad}${e.kalan != null ? ` · ${e.kalan < 0 ? -e.kalan + ' gün önce doldu' : e.kalan + ' gün kaldı'}` : ''}` : e.kategori ? `Doküman Deposu'na "${e.kategori}" kategorisinde yükleyin` : 'Depoda eşleşen belge yok';
|
||
return <span title={ipucu} className={`text-[10px] font-bold px-1.5 py-0.5 rounded shrink-0 ${sinif}`}>{etiket}</span>;
|
||
};
|