Allow retrying item extraction from stored tender documents
This commit is contained in:
@@ -295,3 +295,9 @@ D01 kapsamında `belgeler.isleme` sürümlü migration ile eklendi. Dosyanın ka
|
||||
Doğrulama: 70 otomatik test ve üretim derlemesi geçti. Gerçek HTTP testinde özel firma belgesi, bekleyen ihale metni, yetkisiz istek, bozuk metin → hata → yeniden deneme → kalemsiz sonuç ve ayrı GET üzerinden kalıcı durum kontrol edildi. Mevcut bundle boyutu/import uyarıları sürüyor.
|
||||
|
||||
Sınırlar: Bu paket tam belge yaşam döngüsü kabulü değildir. Tarayıcıda PDF okuma hatasının sunucuya kalıcı aktarımı, ekrandan mevcut belgeyi yeniden işleme, dosya/DB çökme atomikliği ve gerçek resmi dosyalarla uçtan uca kabul halen tamamlanmadı. Canlıya dağıtım yapılmadı.
|
||||
|
||||
### Kaydedilmiş ihale dokümanında yeniden kalem çıkarımı
|
||||
|
||||
İhale detayında okunabilir, erişilebilir ihale dosyalarına yeniden kalem çıkarımı eklendi. Aynı dosya indirilip metni çıkarılıyor veya mevcut sunucu metin çıkarıcısı kullanılıyor; aynı documentId ile yetkili kalem aktarımı yeniden çağrılıyor. Yeni dosya yüklemesi gerekmiyor. İşlem sırasında silme engelleniyor; sonuç veya hata görünür, belge listesi yeniden okunuyor. Liste okuma hatası artık sessizce yutulmuyor; yükleniyor, hata ve yeniden yükle durumları var. Eski liste isteğinin yeni ihale listesini ezmesi istek numarasıyla önleniyor. Belge satırları dar alanda sarılabiliyor.
|
||||
|
||||
Doğrulama: üretim derlemesi geçti; HTTP testi, başarısız çıkarım ve yeniden deneme sonrasında aynı belge kaydının ve orijinal dosya baytlarının korunduğunu kontrol ediyor. Bu düğmenin tarayıcı kabulü, genel Doküman Deposu'na aynı işlemin taşınması ve tarayıcı metin okuma hatasının sunucuda kalıcılaştırılması henüz tamamlanmadı.
|
||||
|
||||
@@ -26,13 +26,25 @@ export const TenderDocuments = ({ tender }) => {
|
||||
const ikn = String(tender?.ikn || '').replace(/[\s\u00a0]+/g, '') || undefined;
|
||||
const [belgeler, setBelgeler] = useState([]);
|
||||
const [yukleniyor, setYukleniyor] = useState(false);
|
||||
const [listeHata, setListeHata] = useState(null);
|
||||
const [listeBekliyor, setListeBekliyor] = useState(false);
|
||||
const listeIstegi = useRef(0);
|
||||
const dosyaRef = useRef(null);
|
||||
|
||||
const belgeleriYukle = useCallback(() => {
|
||||
const belgeleriYukle = useCallback(async () => {
|
||||
if (!ikn) return;
|
||||
listTenderDocuments(ikn).then(setBelgeler).catch(() => {});
|
||||
const request = ++listeIstegi.current;
|
||||
setListeBekliyor(true); setListeHata(null);
|
||||
try {
|
||||
const rows = await listTenderDocuments(ikn);
|
||||
if (request === listeIstegi.current) setBelgeler(rows);
|
||||
} catch (e) {
|
||||
if (request === listeIstegi.current) setListeHata(e.message);
|
||||
} finally {
|
||||
if (request === listeIstegi.current) setListeBekliyor(false);
|
||||
}
|
||||
}, [ikn]);
|
||||
useEffect(() => { belgeleriYukle(); }, [belgeleriYukle]);
|
||||
useEffect(() => { setBelgeler([]); belgeleriYukle(); return () => { listeIstegi.current++; }; }, [belgeleriYukle]);
|
||||
|
||||
// EKAP dokümanı kullanıcının tarayıcısına iniyor; indirdiği dosyayı buraya
|
||||
// yükleyince ihaleye bağlanır ve metni AI analizine verilebilir.
|
||||
@@ -96,6 +108,17 @@ export const TenderDocuments = ({ tender }) => {
|
||||
return text;
|
||||
};
|
||||
|
||||
const kalemleriYenidenCikar = async (b) => {
|
||||
setBusy('kalem'); setError(null); setBilgi(null);
|
||||
try {
|
||||
const metin = await belgeMetni(b);
|
||||
const { kalemCikar } = await import('../services/kalem');
|
||||
const result = await kalemCikar({ ikn, metin, dosya:b.ad, documentId:b.id });
|
||||
setBilgi({ isleme: `${b.ad}: ${result.sayi || 0} kalem çıkarıldı.` });
|
||||
} catch (e) { setError(`Kalem çıkarımı tamamlanamadı: ${e.message}. Dosya kaydı korunmuştur.`); }
|
||||
finally { await belgeleriYukle(); setBusy(null); }
|
||||
};
|
||||
|
||||
// Yüklenen PDF'i AI'ya ver
|
||||
const belgeyiAnalizEt = async (b, mod) => {
|
||||
setBusy('belge'); setError(null);
|
||||
@@ -294,12 +317,7 @@ export const TenderDocuments = ({ tender }) => {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-2.5 flex gap-2">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-red-600 shrink-0 mt-0.5" />
|
||||
<p className="text-[11px] text-red-800 leading-relaxed">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
</>
|
||||
)}
|
||||
@@ -334,15 +352,27 @@ export const TenderDocuments = ({ tender }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-2.5 flex gap-2">
|
||||
<AlertTriangle className="w-3.5 h-3.5 text-red-600 shrink-0 mt-0.5" />
|
||||
<p className="text-[11px] text-red-800 leading-relaxed">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
{bilgi?.isleme && <p role="status" className="text-xs text-slate-700">{bilgi.isleme}</p>}
|
||||
{listeBekliyor && <p role="status" className="text-xs text-slate-500">Belge listesi yükleniyor…</p>}
|
||||
{listeHata && <div role="alert" className="text-xs text-red-700">
|
||||
Belge listesi alınamadı: {listeHata}
|
||||
<button onClick={belgeleriYukle} className="ml-2 underline">Listeyi yeniden yükle</button>
|
||||
</div>}
|
||||
{belgeler.length > 0 && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-[11px] font-bold text-slate-700 flex items-center gap-1.5">
|
||||
<Paperclip className="w-3.5 h-3.5 text-slate-400" /> Bu ihaleye yüklenen belgeler ({belgeler.length})
|
||||
</p>
|
||||
{belgeler.map((b) => (
|
||||
<div key={b.id} className="flex items-center gap-2 bg-white border border-slate-200 rounded-lg px-2.5 py-2">
|
||||
<div key={b.id} className="flex flex-wrap items-center gap-2 bg-white border border-slate-200 rounded-lg px-2.5 py-2">
|
||||
<FileText className="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
||||
<span className="text-xs text-slate-800 truncate flex-1" title={b.ad}>{b.ad}</span><span className="block text-[10px] text-slate-500">{documentProcessingLabel(b.isleme)}</span>
|
||||
<div className="flex-1 min-w-32"><span className="block text-xs text-slate-800 break-all">{b.ad}</span><span className="block text-[10px] text-slate-500">{documentProcessingLabel(b.isleme)}</span></div>
|
||||
<span className="text-[10px] text-slate-400 whitespace-nowrap">
|
||||
{(b.boyut / 1024).toLocaleString('tr-TR', { maximumFractionDigits: 0 })} KB
|
||||
</span>
|
||||
@@ -361,7 +391,14 @@ export const TenderDocuments = ({ tender }) => {
|
||||
>
|
||||
Derin
|
||||
</button>
|
||||
<button onClick={() => belgeSil(b.id)} className="text-slate-300 hover:text-red-600" aria-label="Sil">
|
||||
{b.tur === 'ihale' && okunabilirMi(b.ad) && (
|
||||
<button onClick={() => kalemleriYenidenCikar(b)} disabled={busy !== null || yukleniyor || !b.erisilebilir}
|
||||
aria-label={`${b.ad} için kalem çıkarımını yeniden dene`}
|
||||
className="text-[10px] font-bold text-blue-700 disabled:opacity-40">
|
||||
{busy === 'kalem' ? 'İşleniyor…' : 'Kalemleri yeniden çıkar'}
|
||||
</button>
|
||||
)}
|
||||
<button disabled={busy !== null || yukleniyor} onClick={() => belgeSil(b.id)} className="text-slate-300 hover:text-red-600" aria-label="Sil">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -93,6 +93,10 @@ test('shared item extraction requires an owned tender document and matching tend
|
||||
assert.equal((await processing()).status,'no_items');
|
||||
assert.equal((await processing()).count,0);
|
||||
assert.ok((await processing()).updatedAt);
|
||||
assert.equal((await request('/api/me/documents')).data.belgeler.filter(d=>d.id===tender.data.id).length,1);
|
||||
const retained=await fetch(base+'/api/me/documents/download?id='+tender.data.id,{headers:{Authorization:'Bearer '+token}});
|
||||
assert.equal(retained.status,200);
|
||||
assert.equal(await retained.text(),Buffer.from(body.icerikBase64,'base64').toString());
|
||||
assert.equal((await request('/api/kalem/cikar','POST',{ikn:body.ikn,metin:'test'})).status,403);
|
||||
await request('/api/me/documents?id='+company.data.id,'DELETE');
|
||||
await request('/api/me/documents?id='+tender.data.id,'DELETE');
|
||||
|
||||
Reference in New Issue
Block a user