fix: recheck linked documents after Excel generation

This commit is contained in:
2026-09-06 15:14:29 +03:00
parent 4d6d7009ca
commit bdc9f9dfbf
4 changed files with 39 additions and 14 deletions
+8
View File
@@ -1183,3 +1183,11 @@ Taslak türü seçicisine erişilebilir ad eklendi. Üretim derlemesi geçti. Ö
- Genel toplamın tekrar eden sayfa altlığı olmaması için tfoot normal satır grubu yapıldı; imza bloğuna bölünmeme kuralı eklendi. Bu iki kuralın hesaplanan CSS değerleri doğrulandı; gerçek yazdırma sayfa geçişi ayrıca kabul gerektiriyor.
- Son tarayıcı ölçümü: 657,63 px içerik/tablo genişliği, sıfır taşan hücre, 100 sıralı kalem. Her satırda 3 adet, 125,335 TL birim fiyat ve 376,01 TL tutar; genel toplam 37.601,00 TL. Türkçe metin ve uzun kod bütünüyle korundu. Kanıt `rapor/qa-print-layout.json` ve ekran görüntüsü `rapor/qa-print-layout.png`.
- 275 test ve build başarılı. Desktop Commander ile denenen PDF dönüşümü üretim stilini değiştirdi ve yazdırma düğmesini PDF içine bastı; `qa-100-kalem-before.pdf` üretim çıktısının doğrulama kanıtı değildir. Bu deneme üzerinden üretim sayfalama hatası iddia edilmedi. Doğrulanan kapsam gerçek HTML görünümünde A4 genişliği/taşma/içerik kontrolüdür. Tarayıcıdan kaydedilmiş çok sayfalı PDF ve beş resmi dosya kabulü açık; genel plan tamamlanmadı.
### 2026-09-06 — Excel üretimi sonrasında belge listesini yeniden doğrulama
- Excel üretiminin ilk dosya kontrolü ile dosyanın indirilmesi arasında asenkron çalışma vardı. Son aşamada portföy yeniden okunuyor fakat bağlı belge bilgileri ilk GET yanıtından kullanılıyordu. Bu arada kaldırılmış veya sürümü değişmiş belge eski onayla geçebiliyordu.
- Çalışma kitabı üretildikten sonra belge listesi ve gerekiyorsa portföy yeniden okunuyor. Oturum/form/montaj kontrolünden sonra son belge verisi ekrana aktarılıyor ve ortak belge uygunluk denetimi tekrar yapılıyor. Hata halinde indirme çağrısına geçilmiyor; belge eksikleri ekranda da güncelleniyor.
- QA düzeneğinde sentetik zorunlu firma belgesine bağlı hazır dosya kullanıldı. İlk GET başarılı bırakıldı, Excel oluşturulduktan sonraki ikinci GET görünür test kontrolüyle bekletildi. Ayrı senaryolarda belge boyutu/sürümü değiştirildi ve belge kaydı kaldırıldı. Her ikisinde ikinci GET sayacı 2/bekleyen 1 olarak doğrulandı; yanıt bırakılınca ilgili hata mesajı oluştu ve Excel düğmesi kapandı. 251,00 TL toplam korundu. Dokuz kontrol geçti: `rapor/qa-excel-final-document-check.json`.
- Bu test sentetik belge metadatası kullanır; gerçek firma dosyası silinmedi, dosya indirme dizini izlenmedi. Kanıt uygulamanın son kontrol/hata dalı ve güncellenen hazır durumudur. 275 test ve build başarılı. Genel planın gerçek beş dosya ve dış servis kabul maddeleri açık.
+5 -3
View File
@@ -199,11 +199,13 @@ export const BidPackageBuilder = ({ results, tender: selectedTender, initialFocu
if (tip === 'excel') {
const buffer = await createBidWorkbook(f,satirlar);
if (reviewedText) await validateDocumentTextReview(reviewedText,tender.ikn);
const finalPortfolio=await readPortfolio();
const finalErrors=preparationBlockers(lotSelection.rows,documents,checks,{...currentVault,healthPortfolio:finalPortfolio});
if(finalErrors.length)throw new Error(finalErrors.join(' '));
const [finalFiles,finalPortfolio]=await Promise.all([listDocuments(),readPortfolio()]);
if (!mounted.current || session !== getToken() || submitted !== latestDraft.current)
throw new Error('Form veya oturum değişti; Excel çıktısını yeniden oluşturun.');
const finalVault={...currentVault,documents:finalFiles.belgeler || [],healthPortfolio:finalPortfolio};
setVault(finalVault);
const finalErrors=preparationBlockers(lotSelection.rows,documents,checks,finalVault);
if(finalErrors.length)throw new Error(finalErrors.join(' '));
const finalDeadlineProblem=preparationDeadlineProblem(tender);
if(finalDeadlineProblem)throw new Error(finalDeadlineProblem);
downloadBidWorkbook(buffer,tender.ikn);
+21 -7
View File
@@ -19,6 +19,8 @@ const keyToken=signToken(keyUser);
let apiKeyListFailure=false;
let briefFailure='none';
let holdDocuments=false;
let skipDocumentReads=0,qaFiles=null,outputDocumentReads=0;
const qaFile={id:'qa-owned-file',ad:'QA zorunlu firma belgesi.pdf',tur:'firma',boyut:100,olusturma:'2026-09-06T00:00:00Z',erisilebilir:true};
const pendingDocuments=new Set();
process.env.IHALEIST_USER_DATA_DIR=dir;
const {createBriefOutbox}=await import('../server/brief-outbox.mjs');
@@ -104,14 +106,22 @@ const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{targ
}
if(req.url?.startsWith('/qa-output-documents?')&&req.method==='POST') {
const mode=new URL(req.url,'http://localhost').searchParams.get('mode');
if(mode==='hold')holdDocuments=true;
if(mode==='release'){holdDocuments=false;for(const pending of pendingDocuments){pending.writeHead(200,{'Content-Type':'application/json'});pending.end(JSON.stringify({success:true,belgeler:[]}));}pendingDocuments.clear();}
res.end('Belge yanıtı: '+mode);return;
if(mode==='hold'){holdDocuments=true;skipDocumentReads=0;outputDocumentReads=0;}
if(mode==='hold-final'){holdDocuments=true;skipDocumentReads=1;outputDocumentReads=0;}
if(mode==='revise')qaFiles=qaFiles?.map(file=>({...file,boyut:file.boyut+1}));
if(mode==='remove')qaFiles=[];
if(mode==='release'){holdDocuments=false;for(const pending of pendingDocuments){pending.writeHead(200,{'Content-Type':'application/json'});pending.end(JSON.stringify({success:true,belgeler:qaFiles || []}));}pendingDocuments.clear();}
res.end('Belge yanıtı: '+mode+'; okunan: '+outputDocumentReads+'; bekleyen: '+pendingDocuments.size);return;
}
if(req.url==='/api/me/documents'&&req.method==='GET'&&holdDocuments){pendingDocuments.add(res);res.on('close',()=>pendingDocuments.delete(res));return;}
if(req.url==='/qa-source-update'||req.url==='/qa-source-update?ready=1') {
const selected=req.url.endsWith('?ready=1')?readyBid(tenders[0]):tenders[0];
if(selected.bidDraft){const summary=preparationSummary(selected,{loading:false,error:null,documents:[]});if(summary.issues.length)throw new Error('QA ready fixture: '+JSON.stringify(summary.issues));}
if(req.url==='/api/me/documents'&&req.method==='GET'){
if(holdDocuments){outputDocumentReads++;if(skipDocumentReads>0)skipDocumentReads--;else{pendingDocuments.add(res);res.on('close',()=>pendingDocuments.delete(res));return;}}
if(qaFiles){res.writeHead(200,{'Content-Type':'application/json'});res.end(JSON.stringify({success:true,belgeler:qaFiles}));return;}
}
if(req.url==='/qa-source-update'||req.url==='/qa-source-update?ready=1'||req.url==='/qa-source-update?ready=1&file=1') {
const query=new URL(req.url,'http://localhost').searchParams;
qaFiles=query.has('file')?[{...qaFile}]:null;
const selected=query.has('ready')?readyBid(tenders[0],qaFiles?.[0]):tenders[0];
if(selected.bidDraft){const summary=preparationSummary(selected,{loading:false,error:null,documents:qaFiles || []});if(summary.issues.length)throw new Error('QA ready fixture: '+JSON.stringify(summary.issues));}
const html=`<html lang="tr"><head><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><div id="root"></div><script type="module">
import React from '/node_modules/.vite/deps/react.js';
import ReactDOM from '/node_modules/.vite/deps/react-dom_client.js';
@@ -123,6 +133,10 @@ const ui=await createServer({server:{host:'127.0.0.1',port:0,proxy:{'/api':{targ
React.createElement('h1',{},'Yerel kaynak güncelleme testi'),
React.createElement('p',{},'Yalnız sentetik veri. Açık dosya korunurken güncel kaynak değişir.'),
React.createElement('button',{onClick:()=>control('hold')},'Belge yanıtını beklet'),
React.createElement('button',{onClick:()=>control('hold-final')},'Son belge kontrolünü beklet'),
React.createElement('button',{onClick:()=>control('revise')},'Bağlı belgenin sürümünü değiştir'),
React.createElement('button',{onClick:()=>control('remove')},'Bağlı belgeyi kaldır'),
React.createElement('button',{onClick:()=>control('status')},'Bekleyen belge isteklerini göster'),
React.createElement('button',{onClick:()=>control('release')},'Belge yanıtını serbest bırak'),
React.createElement('p',{role:'status'},status),
React.createElement('button',{onClick:()=>setTenders([{...selected,id:'qa-rotated-source',baslik:'QA Güncellenmiş alım',kazananFirma:'QA Kazanan'}])},'Aynı İKN için yeni kayıt ID ve kazanan yayımla'),
+5 -4
View File
@@ -1,9 +1,10 @@
// Synthetic UI fixture, never a claim of official tender eligibility.
import {sourceRows,bidSourceOptions,bidSourceSnapshot} from '../../src/services/bidSource.js';
import {documentKey,HEALTH_CHECKS} from '../../src/services/preparationChecks.js';
import {documentKey,documentRevision,HEALTH_CHECKS} from '../../src/services/preparationChecks.js';
export function readyBid(tender) {
const document={document:'QA isteğe bağlı belge',mandatory:false,note:'Yalnız sentetik senaryo'};
export function readyBid(tender, file) {
const document={document:file?'QA zorunlu belge':'QA isteğe bağlı belge',mandatory:!!file,note:'Yalnız sentetik senaryo'};
const documentCheck=file?{status:'verified',evidence:'QA belge sürümü kontrol edildi.',documentId:file.id,documentRevision:documentRevision(file)}:{status:'notApplicable',evidence:'QA senaryosunda istenmiyor.'};
const analysis={documentChecklist:[document]},itemSource={};
const partialBid={enabled:false,source:'',selected:[],assignments:{}};
const options=bidSourceOptions(tender,analysis,itemSource);
@@ -14,7 +15,7 @@ export function readyBid(tender) {
teklifTuru:'birim',gecerlilikGun:'120',kisim:'',goturuBedel:'',priceSchema:1,
pricesBySource:Object.fromEntries(rows.map(row=>[row.priceKey,'125.50'])),
sourceSnapshot,reviewedSource:sourceSnapshot,manualDocuments:[],partialBid,itemSource,analysisSource:analysis,
checks:{documents:{[documentKey(document)]:{status:'notApplicable',evidence:'QA senaryosunda istenmiyor.'}},
checks:{documents:{[documentKey(document)]:documentCheck},
products:Object.fromEntries(rows.map(row=>[row.priceKey,{status:'suitable',product:'QA sentetik ürün',source:'QA teknik madde 1',
...Object.fromEntries(HEALTH_CHECKS.map(([key])=>[key,{status:'notApplicable',evidence:'QA senaryosu.'}]))}]))},
}};