Persist document processing outcomes separately from upload success

This commit is contained in:
2026-09-06 09:34:52 +03:00
parent ece623d86c
commit 32ea749669
8 changed files with 72 additions and 10 deletions
+8
View File
@@ -287,3 +287,11 @@ SQLite snapshot, referanslı belge kopyası, SHA-256 manifesti, bütünlük kont
- /api/kalem/cikar artık belge kimliği, sahiplik, ihale türü ve aynı İKN bağı şartlarını kontrol ediyor. Başka hesaba ait dosya, farklı ihale, firma belgesi veya belgesiz metin ortak havuza yazılamıyor. PDF istemcileri yüklenen belge kimliğini iletiyor. - /api/kalem/cikar artık belge kimliği, sahiplik, ihale türü ve aynı İKN bağı şartlarını kontrol ediyor. Başka hesaba ait dosya, farklı ihale, firma belgesi veya belgesiz metin ortak havuza yazılamıyor. PDF istemcileri yüklenen belge kimliğini iletiyor.
- Kanıt: gerçek HTTP akışında firma ve ihale dosyası yükleme; kategori adını değiştirerek yükseltme girişimi; farklı hesap/İKN ve belgesiz çağrı reddi; doğru dokümanla boş cetvel ayrıştırma başarı yolu test edildi. Eski şema migration testinde varsayılan firma türü doğrulandı. 70/70 test ve build geçti. - Kanıt: gerçek HTTP akışında firma ve ihale dosyası yükleme; kategori adını değiştirerek yükseltme girişimi; farklı hesap/İKN ve belgesiz çağrı reddi; doğru dokümanla boş cetvel ayrıştırma başarı yolu test edildi. Eski şema migration testinde varsayılan firma türü doğrulandı. 70/70 test ve build geçti.
- Yayın notu: frontend ve backend birlikte yayımlanmalı; eski istemcinin belgesiz kalem POST’u artık reddedilir. Önceden yüklenmiş resmi dokümanlar otomatik ortak yayın yetkisi kazanmaz; kontrollü yeniden sınıflandırma akışı henüz yok. Geçmişte havuza yazılmış kayıtların köken denetimi ve gerçek PDF/OCR bütünlüğü ayrıca açık. - Yayın notu: frontend ve backend birlikte yayımlanmalı; eski istemcinin belgesiz kalem POST’u artık reddedilir. Önceden yüklenmiş resmi dokümanlar otomatik ortak yayın yetkisi kazanmaz; kontrollü yeniden sınıflandırma akışı henüz yok. Geçmişte havuza yazılmış kayıtların köken denetimi ve gerçek PDF/OCR bütünlüğü ayrıca açık.
### Belge yükleme ve işleme sonuçlarının ayrılması
D01 kapsamında `belgeler.isleme` sürümlü migration ile eklendi. Dosyanın kaydedilmesi artık kalem çıkarımının başarılı olduğu anlamına gelmiyor: bekleyen metin, kalemsiz sonuç, desteklenmeyen biçim, eksik İKN, firma belgesi ve işleme hatası ayrı saklanıp iki belge ekranında gösteriliyor. Yetkisi doğrulanmış çıkarım isteğinin hatası kalıcı kaydediliyor; sonraki başarılı yeniden deneme durumu değiştirebiliyor. Toplu yüklemede liste yenileme hatası kaydedilmiş dosyayı başarısız yükleme olarak etiketlemiyor.
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ı.
+20 -2
View File
@@ -743,21 +743,30 @@ const server = http.createServer(async (req, res) => {
if (url.pathname === '/api/kalem/cikar' && req.method === 'POST') { if (url.pathname === '/api/kalem/cikar' && req.method === 'POST') {
const user = userFromAuthHeader(req.headers['authorization']); const user = userFromAuthHeader(req.headers['authorization']);
if (!user) return json(res, 401, { success: false, error: 'oturum yok' }); if (!user) return json(res, 401, { success: false, error: 'oturum yok' });
let validatedDocument = null;
try { try {
const body = await readBody(req, 4 * 1024 * 1024); const body = await readBody(req, 4 * 1024 * 1024);
if (!body.ikn) return json(res, 400, { success: false, error: 'ikn gerekli' }); if (!body.ikn) return json(res, 400, { success: false, error: 'ikn gerekli' });
const sourceDocument = body.documentId && store.getDocument(user.email,String(body.documentId)); const sourceDocument = body.documentId && store.getDocument(user.email,String(body.documentId));
if (!sourceDocument || sourceDocument.tur !== 'ihale' || sourceDocument.ikn !== String(body.ikn)) if (!sourceDocument || sourceDocument.tur !== 'ihale' || sourceDocument.ikn !== String(body.ikn))
return json(res,403,{success:false,error:'Kalem aktarımı için bu ihaleye bağlı, size ait ihale dokümanı gerekli. Firma belgeleri ortak havuza aktarılamaz.'}); return json(res,403,{success:false,error:'Kalem aktarımı için bu ihaleye bağlı, size ait ihale dokümanı gerekli. Firma belgeleri ortak havuza aktarılamaz.'});
validatedDocument = sourceDocument;
if (typeof body.metin !== 'string') throw new Error('Doküman metni gerekli.');
const { kalemler, yontem } = kalemleriCikar(body.metin || ''); const { kalemler, yontem } = kalemleriCikar(body.metin || '');
if (!kalemler.length) { if (!kalemler.length) {
store.setDocumentProcessing(user.email,sourceDocument.id,{status:'no_items',count:0});
return json(res, 200, { success: true, kalemler: [], yontem, not: 'Metinde teklif cetveli tablosu bulunamadı.' }); return json(res, 200, { success: true, kalemler: [], yontem, not: 'Metinde teklif cetveli tablosu bulunamadı.' });
} }
kalemStore.kalemYaz(String(body.ikn), kalemler, { kalemStore.kalemYaz(String(body.ikn), kalemler, {
dosya: body.dosya ? String(body.dosya).slice(0, 200) : null, yontem, ekleyen: user.email, dosya: body.dosya ? String(body.dosya).slice(0, 200) : null, yontem, ekleyen: user.email,
}); });
store.setDocumentProcessing(user.email,sourceDocument.id,{status:'processed',count:kalemler.length});
return json(res, 200, { success: true, kalemler, yontem, sayi: kalemler.length }); return json(res, 200, { success: true, kalemler, yontem, sayi: kalemler.length });
} catch (e) { } catch (e) {
if (validatedDocument) {
try { store.setDocumentProcessing(user.email,validatedDocument.id,{status:'error',error:'Kalem çıkarımı tamamlanamadı. Doküman metnini kontrol edip yeniden deneyin.'}); }
catch (statusError) { console.error('[document-processing] Durum kaydedilemedi:', statusError.message); }
}
return json(res, 400, { success: false, error: e.message }); return json(res, 400, { success: false, error: e.message });
} }
} }
@@ -970,8 +979,9 @@ const server = http.createServer(async (req, res) => {
// PDF tarayıcıda okunduğu için istemci ikn'yi kendisi gönderir. // PDF tarayıcıda okunduğu için istemci ikn'yi kendisi gönderir.
let ikn = body.ikn ? String(body.ikn).slice(0, 40) : null; let ikn = body.ikn ? String(body.ikn).slice(0, 40) : null;
let ofisMetni = null; let ofisMetni = null;
let readError = null;
if (sunucuOkuyabilir(body.ad)) { if (sunucuOkuyabilir(body.ad)) {
try { ofisMetni = arsivMetni(String(body.ad), buf); } catch { /* metin çıkmazsa devam */ } try { ofisMetni = arsivMetni(String(body.ad), buf); } catch { readError = 'Dosya metni okunamadı. Dosya biçimini ve bütünlüğünü kontrol edin.'; }
if (documentType === 'ihale' && !ikn && body.otoIkn && ofisMetni) ikn = iknCikar(ofisMetni); if (documentType === 'ihale' && !ikn && body.otoIkn && ofisMetni) ikn = iknCikar(ofisMetni);
} }
const id = store.saveDocumentFile(user.email, { const id = store.saveDocumentFile(user.email, {
@@ -986,19 +996,27 @@ const server = http.createServer(async (req, res) => {
// tıklama beklemez. PDF'ler tarayıcıda çözüldüğü için istemci // tıklama beklemez. PDF'ler tarayıcıda çözüldüğü için istemci
// metni /api/kalem/cikar'a gönderir. // metni /api/kalem/cikar'a gönderir.
let kalemBilgi = null; let kalemBilgi = null;
let processing = documentType !== 'ihale' ? {status:'not_applicable'}
: readError ? {status:'error',error:readError}
: /\.(pdf|txt|md)$/i.test(body.ad) ? {status:'awaiting_text'}
: !sunucuOkuyabilir(body.ad) ? {status:'unsupported'}
: !ikn ? {status:'needs_tender'} : {status:'no_items',count:0};
if (documentType === 'ihale' && ikn && ofisMetni) { if (documentType === 'ihale' && ikn && ofisMetni) {
try { try {
const { kalemler, yontem } = kalemleriCikar(ofisMetni); const { kalemler, yontem } = kalemleriCikar(ofisMetni);
if (kalemler.length) { if (kalemler.length) {
kalemStore.kalemYaz(ikn, kalemler, { dosya: String(body.ad), yontem, ekleyen: user.email }); kalemStore.kalemYaz(ikn, kalemler, { dosya: String(body.ad), yontem, ekleyen: user.email });
kalemBilgi = { sayi: kalemler.length, yontem }; kalemBilgi = { sayi: kalemler.length, yontem };
processing = {status:'processed',count:kalemler.length};
console.log(`[kalem] ${ikn} → ${kalemler.length} kalem (${yontem}, ${body.ad})`); console.log(`[kalem] ${ikn} → ${kalemler.length} kalem (${yontem}, ${body.ad})`);
} }
} catch (e) { } catch (e) {
processing = {status:'error',error:'Kalem çıkarımı tamamlanamadı; yeniden deneyin.'};
console.warn('[kalem] çıkarılamadı:', e.message); console.warn('[kalem] çıkarılamadı:', e.message);
} }
} }
return json(res, 200, { success: true, id, ikn, kalem: kalemBilgi, belgeler: store.listDocuments(user.email), kullanim: store.documentUsage(user.email) }); store.setDocumentProcessing(user.email,id,processing);
return json(res, 200, { success: true, id, ikn, processing, kalem: kalemBilgi, belgeler: store.listDocuments(user.email), kullanim: store.documentUsage(user.email) });
} catch (e) { } catch (e) {
return json(res, e.kod === 'KOTA_ASILDI' ? 402 : 400, { success: false, error: e.message, kod: e.kod || null }); return json(res, e.kod === 'KOTA_ASILDI' ? 402 : 400, { success: false, error: e.message, kod: e.kod || null });
} }
+10 -2
View File
@@ -98,12 +98,15 @@ export function openDb() {
if (!columns.has(column)) db.exec(`ALTER TABLE belgeler ADD COLUMN ${column} TEXT`); if (!columns.has(column)) db.exec(`ALTER TABLE belgeler ADD COLUMN ${column} TEXT`);
} }
if (!columns.has('tur')) db.exec("ALTER TABLE belgeler ADD COLUMN tur TEXT NOT NULL DEFAULT 'firma'"); if (!columns.has('tur')) db.exec("ALTER TABLE belgeler ADD COLUMN tur TEXT NOT NULL DEFAULT 'firma'");
if (!columns.has('isleme')) db.exec('ALTER TABLE belgeler ADD COLUMN isleme TEXT');
db.exec('CREATE INDEX IF NOT EXISTS idx_belge_ikn ON belgeler(email, ikn)'); db.exec('CREATE INDEX IF NOT EXISTS idx_belge_ikn ON belgeler(email, ikn)');
db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (version TEXT PRIMARY KEY, applied_at TEXT NOT NULL)`); db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations (version TEXT PRIMARY KEY, applied_at TEXT NOT NULL)`);
db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)') db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)')
.run('20260906_document_columns', new Date().toISOString()); .run('20260906_document_columns', new Date().toISOString());
db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)') db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)')
.run('20260906_document_type', new Date().toISOString()); .run('20260906_document_type', new Date().toISOString());
db.prepare('INSERT OR IGNORE INTO schema_migrations VALUES (?,?)')
.run('20260906_document_processing', new Date().toISOString());
db.exec('COMMIT'); db.exec('COMMIT');
} catch (error) { } catch (error) {
try { db.exec('ROLLBACK'); } catch { /* original error is authoritative */ } try { db.exec('ROLLBACK'); } catch { /* original error is authoritative */ }
@@ -269,11 +272,16 @@ export function saveDocumentFile(email, metadata, bytes) {
} }
export function listDocuments(email, { ikn } = {}) { export function listDocuments(email, { ikn } = {}) {
const alanlar = 'id, ad, kategori, boyut, mime, son_kullanma, ikn, olusturma, dosya, tur'; const alanlar = 'id, ad, kategori, boyut, mime, son_kullanma, ikn, olusturma, dosya, tur, isleme';
return withDb((db) => (ikn return withDb((db) => (ikn
? db.prepare(`SELECT ${alanlar} FROM belgeler WHERE email=? AND ikn=? ORDER BY olusturma DESC`).all(email, ikn) ? db.prepare(`SELECT ${alanlar} FROM belgeler WHERE email=? AND ikn=? ORDER BY olusturma DESC`).all(email, ikn)
: db.prepare(`SELECT ${alanlar} FROM belgeler WHERE email=? ORDER BY olusturma DESC`).all(email) : db.prepare(`SELECT ${alanlar} FROM belgeler WHERE email=? ORDER BY olusturma DESC`).all(email)
).map(({dosya,...record}) => ({...record, erisilebilir: fs.existsSync(path.join(BELGE_DIR,dosya))})), []); ).map(({dosya,isleme,...record}) => ({...record, isleme:isleme ? JSON.parse(isleme) : null, erisilebilir: fs.existsSync(path.join(BELGE_DIR,dosya))})), []);
}
export function setDocumentProcessing(email,id,processing) {
return withDb(db=>db.prepare('UPDATE belgeler SET isleme=? WHERE id=? AND email=?')
.run(JSON.stringify({...processing,updatedAt:now()}),id,email).changes>0);
} }
export function getDocument(email, id) { export function getDocument(email, id) {
+6 -3
View File
@@ -1,3 +1,4 @@
import { documentProcessingLabel } from '../services/documentProcessing';
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useApp } from '../context/AppContext'; import { useApp } from '../context/AppContext';
import { getToken } from '../services/auth'; import { getToken } from '../services/auth';
@@ -49,16 +50,17 @@ export const TenderDocuments = ({ tender }) => {
// otomatik çıkarır. PDF tarayıcıda çözüldüğü için metni biz göndeririz. // otomatik çıkarır. PDF tarayıcıda çözüldüğü için metni biz göndeririz.
const cevap = await uploadDocument(f, 'İhale Dokümanı', ikn, {documentType:'ihale'}); const cevap = await uploadDocument(f, 'İhale Dokümanı', ikn, {documentType:'ihale'});
let kalem = cevap?.kalem?.sayi || null; let kalem = cevap?.kalem?.sayi || null;
let kalemHata = cevap.processing?.status === 'error' ? cevap.processing.error : null;
if (!kalem && ikn && /\.(pdf|txt|md)$/i.test(f.name)) { if (!kalem && ikn && /\.(pdf|txt|md)$/i.test(f.name)) {
try { try {
const { text } = await extractText(f); const { text } = await extractText(f);
const { kalemCikar } = await import('../services/kalem'); const { kalemCikar } = await import('../services/kalem');
const d = await kalemCikar({ ikn, metin: text, dosya: f.name, documentId:cevap.id }); const d = await kalemCikar({ ikn, metin: text, dosya: f.name, documentId:cevap.id });
kalem = d.sayi || null; kalem = d.sayi || null;
} catch { /* kalem çıkmazsa yükleme yine de başarılı sayılır */ } } catch (error) { kalemHata = error.message; }
} }
belgeleriYukle(); belgeleriYukle();
setBilgi({ yuklendi: f.name, kalem }); setBilgi({ yuklendi: f.name, kalem, kalemHata });
} catch (err) { } catch (err) {
setError(err.kod === 'KOTA_ASILDI' ? err.message : `Yüklenemedi: ${err.message}`); setError(err.kod === 'KOTA_ASILDI' ? err.message : `Yüklenemedi: ${err.message}`);
} finally { setYukleniyor(false); } } finally { setYukleniyor(false); }
@@ -283,6 +285,7 @@ export const TenderDocuments = ({ tender }) => {
{bilgi.yuklendi && ( {bilgi.yuklendi && (
<> <>
<strong>{bilgi.yuklendi}</strong> yüklendi ve bu ihaleye bağlandı. <strong>{bilgi.yuklendi}</strong> yüklendi ve bu ihaleye bağlandı.
{bilgi.kalemHata && <span role="alert"> Kalem aktarımı tamamlanamadı: {bilgi.kalemHata}. Dosya kaydı korunmuştur.</span>}
{bilgi.kalem {bilgi.kalem
? <> Teklif cetvelinden <strong>{bilgi.kalem} kalem</strong> otomatik çıkarıldı; bu ihale artık kalemleriyle listeleniyor.</> ? <> Teklif cetvelinden <strong>{bilgi.kalem} kalem</strong> otomatik çıkarıldı; bu ihale artık kalemleriyle listeleniyor.</>
: <> Bu dosyadan teklif cetveli kalemi çıkarılamadı.</>} : <> Bu dosyadan teklif cetveli kalemi çıkarılamadı.</>}
@@ -339,7 +342,7 @@ export const TenderDocuments = ({ tender }) => {
{belgeler.map((b) => ( {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 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" /> <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="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>
<span className="text-[10px] text-slate-400 whitespace-nowrap"> <span className="text-[10px] text-slate-400 whitespace-nowrap">
{(b.boyut / 1024).toLocaleString('tr-TR', { maximumFractionDigits: 0 })} KB {(b.boyut / 1024).toLocaleString('tr-TR', { maximumFractionDigits: 0 })} KB
</span> </span>
+8 -2
View File
@@ -1,3 +1,4 @@
import { documentProcessingLabel } from '../../services/documentProcessing';
import React, { useState, useEffect, useRef } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { useApp } from '../../context/AppContext'; import { useApp } from '../../context/AppContext';
import { listDocuments, uploadDocument, deleteDocument, downloadDocument, formatBayt, updateDocumentMeta } from '../../services/account'; import { listDocuments, uploadDocument, deleteDocument, downloadDocument, formatBayt, updateDocumentMeta } from '../../services/account';
@@ -67,14 +68,17 @@ export const DocumentVault = () => {
const cevap = await uploadDocument(f, 'İhale Dokümanı', ikn, { otoIkn: true, documentType:'ihale' }); const cevap = await uploadDocument(f, 'İhale Dokümanı', ikn, { otoIkn: true, documentType:'ihale' });
ikn = cevap?.ikn || ikn; ikn = cevap?.ikn || ikn;
let kalem = cevap?.kalem?.sayi || null; let kalem = cevap?.kalem?.sayi || null;
let kalemHata = cevap.processing?.status === 'error' ? cevap.processing.error : null;
if (ikn && !kalem && metin) { if (ikn && !kalem && metin) {
try { try {
const { kalemCikar } = await import('../../services/kalem'); const { kalemCikar } = await import('../../services/kalem');
kalem = (await kalemCikar({ ikn, metin, dosya: f.name, documentId:cevap.id })).sayi || null; kalem = (await kalemCikar({ ikn, metin, dosya: f.name, documentId:cevap.id })).sayi || null;
} catch { /* kalem çıkmazsa yükleme yine başarılı */ } } catch (error) { kalemHata = error.message; }
} }
guncelle(i, { durum: 'tamam', ikn, kalem }); guncelle(i, { durum: 'tamam', ikn, kalem, kalemHata });
setDurum(cevap); setDurum(cevap);
try { setDurum(await listDocuments()); }
catch { setHata('Dosya kayıtlı; güncel belge listesi alınamadı. Listeyi yeniden yükleyin.'); }
} catch (err) { } catch (err) {
guncelle(i, { durum: 'hata', hata: err.message }); guncelle(i, { durum: 'hata', hata: err.message });
} }
@@ -218,6 +222,7 @@ export const DocumentVault = () => {
{t.ikn {t.ikn
? <span className="font-mono font-bold text-amber-300">{t.ikn}</span> ? <span className="font-mono font-bold text-amber-300">{t.ikn}</span>
: <span className="text-amber-200/80">İKN bulunamadı — ihaleye bağlanmadı</span>} : <span className="text-amber-200/80">İKN bulunamadı — ihaleye bağlanmadı</span>}
{t.kalemHata && <span role="alert" className="text-amber-200">Dosya kayıtlı; kalem aktarımı başarısız: {t.kalemHata}</span>}
{t.kalem ? <span className="text-emerald-300 font-bold flex items-center gap-1"><Package className="w-3 h-3" />{t.kalem} kalem</span> : null} {t.kalem ? <span className="text-emerald-300 font-bold flex items-center gap-1"><Package className="w-3 h-3" />{t.kalem} kalem</span> : null}
</span> </span>
)} )}
@@ -254,6 +259,7 @@ export const DocumentVault = () => {
</p> </p>
<p className="text-[10px] text-slate-400 mt-0.5"> <p className="text-[10px] text-slate-400 mt-0.5">
{new Date(d.olusturma).toLocaleString('tr-TR')} {new Date(d.olusturma).toLocaleString('tr-TR')}
<span className="block">{documentProcessingLabel(d.isleme)}</span>
</p> </p>
<label className="flex items-center gap-1.5 mt-1.5 text-[10px] text-slate-500"> <label className="flex items-center gap-1.5 mt-1.5 text-[10px] text-slate-500">
<CalendarClock className={`w-3 h-3 ${kalanGun(d) != null && kalanGun(d) < 0 ? 'text-red-600' : kalanGun(d) != null && kalanGun(d) <= 30 ? 'text-amber-600' : 'text-slate-400'}`} /> <CalendarClock className={`w-3 h-3 ${kalanGun(d) != null && kalanGun(d) < 0 ? 'text-red-600' : kalanGun(d) != null && kalanGun(d) <= 30 ? 'text-amber-600' : 'text-slate-400'}`} />
+10
View File
@@ -0,0 +1,10 @@
export function documentProcessingLabel(processing) {
const labels={processed:`Kalem çıkarımı tamamlandı (${processing?.count || 0} kalem)`,
no_items:'Kalem çıkarılamadı — resmi cetveli kontrol edin',
awaiting_text:'Dosya kayıtlı; metin/kalem aktarımı bekleniyor',
unsupported:'Dosya kayıtlı; bu biçimde kalem çıkarımı desteklenmiyor',
needs_tender:'Dosya kayıtlı; İKN bağlantısı gerekli',
not_applicable:'Özel firma belgesi — ortak kalem aktarımı yapılmaz',
error:processing?.error || 'Dosya kayıtlı; kalem çıkarımında hata oluştu'};
return labels[processing?.status] || 'İşleme durumu doğrulanmadı';
}
+9
View File
@@ -74,6 +74,7 @@ test('shared item extraction requires an owned tender document and matching tend
const body={ad:'scope.txt',icerikBase64:Buffer.from('Yalnız test dokümanı, cetvel yok.').toString('base64'),ikn:'2026/123456'}; const body={ad:'scope.txt',icerikBase64:Buffer.from('Yalnız test dokümanı, cetvel yok.').toString('base64'),ikn:'2026/123456'};
const company=await request('/api/me/documents','POST',body); const company=await request('/api/me/documents','POST',body);
assert.equal(company.status,200); assert.equal(company.status,200);
assert.equal(company.data.processing.status,'not_applicable');
assert.equal(company.data.belgeler.find(d=>d.id===company.data.id).tur,'firma'); assert.equal(company.data.belgeler.find(d=>d.id===company.data.id).tur,'firma');
const extract=id=>({documentId:id,ikn:body.ikn,metin:'Cetvel yok.'}); const extract=id=>({documentId:id,ikn:body.ikn,metin:'Cetvel yok.'});
assert.equal((await request('/api/kalem/cikar','POST',extract(company.data.id))).status,403); assert.equal((await request('/api/kalem/cikar','POST',extract(company.data.id))).status,403);
@@ -81,9 +82,17 @@ test('shared item extraction requires an owned tender document and matching tend
assert.equal((await request('/api/kalem/cikar','POST',extract(company.data.id))).status,403); assert.equal((await request('/api/kalem/cikar','POST',extract(company.data.id))).status,403);
const tender=await request('/api/me/documents','POST',{...body,documentType:'ihale'}); const tender=await request('/api/me/documents','POST',{...body,documentType:'ihale'});
assert.equal(tender.status,200); assert.equal(tender.status,200);
assert.equal(tender.data.processing.status,'awaiting_text');
const processing=async()=> (await request('/api/me/documents')).data.belgeler.find(d=>d.id===tender.data.id).isleme;
assert.equal((await request('/api/kalem/cikar','POST',extract(tender.data.id),other)).status,403); assert.equal((await request('/api/kalem/cikar','POST',extract(tender.data.id),other)).status,403);
assert.equal((await request('/api/kalem/cikar','POST',{...extract(tender.data.id),ikn:'2026/999999'})).status,403); assert.equal((await request('/api/kalem/cikar','POST',{...extract(tender.data.id),ikn:'2026/999999'})).status,403);
assert.equal((await processing()).status,'awaiting_text');
assert.equal((await request('/api/kalem/cikar','POST',{...extract(tender.data.id),metin:{invalid:true}})).status,400);
assert.equal((await processing()).status,'error');
assert.equal((await request('/api/kalem/cikar','POST',extract(tender.data.id))).status,200); assert.equal((await request('/api/kalem/cikar','POST',extract(tender.data.id))).status,200);
assert.equal((await processing()).status,'no_items');
assert.equal((await processing()).count,0);
assert.ok((await processing()).updatedAt);
assert.equal((await request('/api/kalem/cikar','POST',{ikn:body.ikn,metin:'test'})).status,403); 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='+company.data.id,'DELETE');
await request('/api/me/documents?id='+tender.data.id,'DELETE'); await request('/api/me/documents?id='+tender.data.id,'DELETE');
+1 -1
View File
@@ -15,7 +15,7 @@ after(() => rmSync(dir, { recursive: true, force: true }));
test('old volume migrates before indexing, preserves files and migration is repeatable', () => { test('old volume migrates before indexing, preserves files and migration is repeatable', () => {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
const db = store.openDb(); const db = store.openDb();
assert.equal(db.prepare('SELECT COUNT(*) n FROM schema_migrations').get().n, 2); assert.equal(db.prepare('SELECT COUNT(*) n FROM schema_migrations').get().n, 3);
assert.ok(db.prepare('PRAGMA table_info(belgeler)').all().some(c => c.name === 'ikn')); assert.ok(db.prepare('PRAGMA table_info(belgeler)').all().some(c => c.name === 'ikn'));
db.close(); db.close();
} }