Add source-comparison form for versioned document text reviews
This commit is contained in:
@@ -359,3 +359,9 @@ Belge sahibinin OCR/metin incelemesini kaydetmesi için document_text_reviews ta
|
||||
Doğrulama: mevcut 78 test ve üretim derlemesi geçti; ek kaynak-dosyası-değişimi testi de geçti (toplam 79 ayrı test). HTTP testinde sahiplik, zorunlu karşılaştırma, kalıcı okuma ve eski sürüm reddi; ek testte gerçek dosya baytları değişince incelemenin stale olması ve belge silinince geçmişin kaldırılması doğrulandı.
|
||||
|
||||
Bu yalnız sunucu altyapısıdır. Metin düzenleme arayüzü, bu kaydın analizde sunucu tarafından çözülmesi ve kontrollü hazırlık ilerlemesi henüz bağlanmadı; mevcut OCR engeli sürüyor. Kullanıcı beyanı hukuki veya otomatik doğruluk garantisi değildir. Gerçek resmi dosya kabulü açık.
|
||||
|
||||
### Metin inceleme formu
|
||||
|
||||
İhale dokümanı “Metni incele” paneline DocumentTextReview eklendi. Kaynakla karşılaştırılmış düzeltme metni, sayfa/düzeltme açıklaması ve tüm sayfaları karşılaştırma beyanı alınır. Metin veya not değişince beyan sıfırlanır. Başarısız kayıt taslağı korur; sürüm uyuşmazlığında kullanıcıya taslağını kopyalayıp güncel kaydı yükleme seçeneği sunulur. Son kayıt sürümü/zamanı ve kaynak değişikliği açık gösterilir. Kayıt başarısı sunucu yanıtıyla gösterilir; bileşen kapanması/oturum değişiminden sonraki yanıtlar uygulanmaz.
|
||||
|
||||
79 test ve üretim derlemesi geçti. Formun gerçek tarayıcı kabulü ve kaydedilen incelemenin analizde sunucu tarafından çözülmesi henüz tamamlanmadı. Form, kayıt işleminin tek başına hazır teklif üretmediğini açıkça belirtiyor; mevcut OCR engeli devam ediyor.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import React,{useEffect,useRef,useState} from 'react';
|
||||
import {getDocumentTextReview,saveDocumentTextReview} from '../services/account';
|
||||
import {getToken} from '../services/auth';
|
||||
|
||||
export function DocumentTextReview({documentId,extractedText}) {
|
||||
const [source,setSource]=useState(null);
|
||||
const [text,setText]=useState(extractedText || '');
|
||||
const [note,setNote]=useState('');
|
||||
const [compared,setCompared]=useState(false);
|
||||
const [busy,setBusy]=useState(false);
|
||||
const [error,setError]=useState(null);
|
||||
const [saved,setSaved]=useState(null);
|
||||
const live=useRef(true);
|
||||
const sequence=useRef(0);
|
||||
const load=async()=>{
|
||||
const request=++sequence.current, token=getToken();
|
||||
setBusy(true);setError(null);
|
||||
try {
|
||||
const data=await getDocumentTextReview(documentId);
|
||||
if(!live.current || request!==sequence.current || token!==getToken())return;
|
||||
setSource(data);
|
||||
// A stale source must be compared afresh; do not seed from an old correction.
|
||||
setText(!data.stale && data.review ? data.review.corrected_text : extractedText || '');
|
||||
setNote(!data.stale && data.review ? data.review.review_note : '');
|
||||
setCompared(false);setSaved(null);
|
||||
} catch(e) {if(live.current && request===sequence.current && token===getToken())setError(e.message);}
|
||||
finally {if(live.current && request===sequence.current && token===getToken())setBusy(false);}
|
||||
};
|
||||
useEffect(()=>{live.current=true;load();return()=>{live.current=false;sequence.current++;};},[documentId]);
|
||||
const save=async()=>{
|
||||
if(!source || busy)return;
|
||||
const token=getToken();setBusy(true);setError(null);setSaved(null);
|
||||
try {
|
||||
const result=await saveDocumentTextReview(documentId,{fileHash:source.fileHash,
|
||||
expectedRevision:source.review?.revision || 0,correctedText:text,note,allPagesCompared:compared});
|
||||
if(!live.current || token!==getToken())return;
|
||||
setSource(result);setSaved(result.review);setCompared(false);
|
||||
} catch(e) {if(live.current && token===getToken())setError(e.message);}
|
||||
finally {if(live.current && token===getToken())setBusy(false);}
|
||||
};
|
||||
return <section aria-label="Belge metni incelemesi" className="border-t pt-3 space-y-2">
|
||||
<h5 className="font-bold text-sm">Metni karşılaştır ve düzelt</h5>
|
||||
<p className="text-xs text-slate-600">Belgenin tüm sayfalarını açıp metin, miktar ve birimleri karşılaştırın. Bu kayıt kullanıcı incelemesidir; resmî onay veya e-imza değildir. Kaydetmek tek başına teklif dosyasını hazır yapmaz.</p>
|
||||
{source?.stale && <p role="alert" className="text-xs text-amber-800">Kaynak dosya değişmiş. Önceki inceleme geçerli değil; tüm sayfaları yeniden karşılaştırın.</p>}
|
||||
{source?.review && <p className="text-xs text-slate-500">Son kayıt: sürüm {source.review.revision} · {new Date(source.review.reviewed_at).toLocaleString('tr-TR')}</p>}
|
||||
<label className="block text-xs font-semibold">Kontrol edilen metin
|
||||
<textarea value={text} onChange={e=>{setText(e.target.value);setCompared(false);setSaved(null);}} disabled={busy}
|
||||
className="block w-full h-64 border rounded p-2 font-mono font-normal mt-1" />
|
||||
</label>
|
||||
<label className="block text-xs font-semibold">Karşılaştırılan sayfalar ve düzeltmeler
|
||||
<textarea value={note} onChange={e=>{setNote(e.target.value);setCompared(false);setSaved(null);}} disabled={busy}
|
||||
placeholder="Örnek: Sayfa 1–3 karşılaştırıldı; 2. sayfadaki miktar 100 olarak düzeltildi."
|
||||
className="block w-full border rounded p-2 font-normal mt-1" />
|
||||
</label>
|
||||
<label className="flex gap-2 text-xs"><input type="checkbox" checked={compared} onChange={e=>setCompared(e.target.checked)} disabled={busy}/>
|
||||
Bu metni kaynak dosyanın tüm sayfalarıyla karşılaştırdım.</label>
|
||||
<button onClick={save} disabled={busy || !source || !compared || text.trim().length<40 || note.trim().length<20}
|
||||
className="text-xs bg-blue-700 text-white px-3 py-2 rounded disabled:opacity-40">{busy ? 'İşleniyor…' : 'İncelemeyi kaydet'}</button>
|
||||
{error && <div role="alert" className="text-xs text-red-700">{error}
|
||||
<p>Metin taslağınız bu ekranda korunuyor. Sürüm değiştiyse taslağı kopyalayıp güncel kaydı yükleyin.</p>
|
||||
<button onClick={load} disabled={busy} className="underline">Güncel kaydı yükle (taslağı değiştirir)</button>
|
||||
</div>}
|
||||
{saved && <p role="status" className="text-xs text-green-800">İnceleme sürüm {saved.revision} sunucuda kaydedildi. Analiz ve teklif kontrolleri ayrıca tamamlanmalıdır.</p>}
|
||||
</section>;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { DocumentTextReview } from './DocumentTextReview';
|
||||
import { readableDocument, readStoredDocument, inspectStoredDocument, retryDocumentExtraction } from '../services/storedDocument';
|
||||
import { documentProcessingLabel } from '../services/documentProcessing';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
@@ -101,7 +102,7 @@ export const TenderDocuments = ({ tender }) => {
|
||||
try {
|
||||
const result = await inspectStoredDocument(b);
|
||||
if (token !== getToken() || request !== onizlemeIstegi.current) return;
|
||||
setMetinOnizleme({...result,name:b.ad});
|
||||
setMetinOnizleme({...result,name:b.ad,documentId:b.id,type:b.tur});
|
||||
} catch (e) { if (token === getToken() && request === onizlemeIstegi.current) setError(e.message); }
|
||||
finally { if (token === getToken() && request === onizlemeIstegi.current) setBusy(null); }
|
||||
};
|
||||
@@ -419,6 +420,7 @@ export const TenderDocuments = ({ tender }) => {
|
||||
Metni resmi dokümanla karşılaştırın; okunmuş olması doğruluğunun onaylandığı anlamına gelmez.
|
||||
</p>
|
||||
<pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words text-xs">{metinOnizleme.text || 'Okunabilir metin bulunamadı.'}</pre>
|
||||
{metinOnizleme.type === 'ihale' && <DocumentTextReview key={metinOnizleme.documentId} documentId={metinOnizleme.documentId} extractedText={metinOnizleme.text} />}
|
||||
<button onClick={()=>setMetinOnizleme(null)} className="text-xs underline">Metin önizlemesini kapat</button>
|
||||
</section>}
|
||||
|
||||
|
||||
@@ -114,3 +114,8 @@ export const saveTenderState = (key, patch, snapshot, version) =>
|
||||
|
||||
export const reportDocumentReadError = (id, expectedProcessing) =>
|
||||
iste('/api/me/documents/read-error', { method:'POST', body:JSON.stringify({id,expectedProcessing:expectedProcessing ?? null}) });
|
||||
|
||||
export const getDocumentTextReview = (id) => iste(`/api/me/documents/text-review?id=${encodeURIComponent(id)}`);
|
||||
export const saveDocumentTextReview = (id, data) => iste('/api/me/documents/text-review', {
|
||||
method:'POST', body:JSON.stringify({id,...data}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user