diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index c71e56b..f24ae6c 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -108,3 +108,7 @@ Tarayıcı kanıtı: [Yenileme sonrası beş kart](evidence/qa-kanban-after-relo - D11: küçük sürümlerle yayın, yedek/geri dönüş ve canlı ölçümler. Kod testleri bütün planın tamamlandığını kanıtlamaz. Tarayıcı kanıtı şu an yalnız sentetik görev kalıcılığı senaryosunu kapsar; kapsamlı uçtan uca ve canlı kabul henüz yoktur. + +## Çıktı doğrulaması sırasındaki değişiklikler + +Dosya kontrolü sürerken form/kaynak veya oturum değişirse çıktı iptal edilir. Kontrol penceresi tıklama sırasında açılır; doğrulama hatasında kapatılır. Aynı anda ikinci çıktı başlatılmaz. Taslak kaydı sürerken yapılan yeni değişiklikler, önceki kaydın yanıtı geldiğinde kaydedilmiş sayılmaz. Mevcut 35 test ve derleme geçti; bu yarış senaryolarının gecikmeli tarayıcı kabulü ayrıca bekliyor. diff --git a/src/components/BidPackageBuilder.jsx b/src/components/BidPackageBuilder.jsx index 556fb54..fc7ccc4 100644 --- a/src/components/BidPackageBuilder.jsx +++ b/src/components/BidPackageBuilder.jsx @@ -1,6 +1,7 @@ +import { getToken } from '../services/auth'; import { listDocuments, downloadDocument } from '../services/account'; import { tenderDateKey } from '../services/tenderCalendar'; -import React, { useState, useMemo, useEffect } from 'react'; +import React, { useState, useMemo, useEffect, useRef } from 'react'; import { bidRowTotal, validateBidRows, roundMoney, parseBidPrice } from '../services/bidValidation'; import { sourceRows, bidSourceSnapshot, restoreBidPrices } from '../services/bidSource'; import { rowLot, selectBidLots } from '../services/bidLots'; @@ -100,9 +101,16 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => { ); const kalemToplami = useMemo(() => roundMoney(satirlar.reduce((t,r) => t + (bidRowTotal(r) ?? 0),0)), [satirlar]); + const latestDraft = useRef(''); + latestDraft.current = JSON.stringify([firma,teklifTuru,gecerlilikGun,kisim,goturuBedel,fiyatlar,sourceSnapshot,reviewedSource,checks,manualDocuments,partialBid]); + const mounted = useRef(true); + const printWindow = useRef(null); + const [printing,setPrinting] = useState(false); + useEffect(()=>{mounted.current=true;return ()=>{mounted.current=false;printWindow.current?.close();};},[]); const saveDraft = async () => { + const submitted = latestDraft.current; const saved = await mutateTender(tender.id, {bidDraft:{firma,teklifTuru,gecerlilikGun,kisim,goturuBedel,priceSchema:1,pricesBySource:fiyatlar,sourceSnapshot,reviewedSource,checks,manualDocuments,partialBid,analysisSource:{itemsAndSpecs:effectiveResults.itemsAndSpecs || [],documentChecklist:effectiveResults.documentChecklist || [],documentVersion:effectiveResults.documentVersion || null}}}); - if (saved) { setDirty(false); setHata(null); } + if (saved && mounted.current) { setDirty(latestDraft.current !== submitted); setHata(null); } else setHata('Taslak kaydedilemedi. Tekrar deneyin.'); }; @@ -127,21 +135,32 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => { }); const uret = async (tip) => { + if (printWindow.current) return; + const submitted = latestDraft.current; + const session = getToken(); setHata(null); try { if (!hazir) throw new Error('Kalem, miktar, birim, fiyat, firma ve geçerlilik süresindeki eksikleri giderin.'); + // Open during the click; async validation must not lose popup activation. + printWindow.current = belgeYazdir('Dosya kontrolü', '
Belgeler kontrol ediliyor…
'); + setPrinting(true); const currentFiles = await listDocuments(); + if (!mounted.current || session !== getToken() || submitted !== latestDraft.current) + throw new Error('Form, kaynak veya oturum değişti. Güncel bilgilerle yeniden çıktı oluşturun.'); const currentVault = {loading:false,error:null,documents:currentFiles.belgeler || [],deadlineDate:tenderDateKey(tender)}; setVault(currentVault); const fileErrors = preparationBlockers(lotSelection.rows, documents, checks, currentVault); if (fileErrors.length) throw new Error(fileErrors.join(' ')); const f = formVerisi(); - if (tip === 'mektup') belgeYazdir('Teklif Mektubu', teklifMektubuHtml(f, toplam)); - if (tip === 'cetvel') belgeYazdir('Birim Fiyat Teklif Cetveli', teklifCetveliHtml(f, satirlar, toplam)); - if (tip === 'liste') belgeYazdir('Evrak Kontrol Listesi', evrakListesiHtml(f, documents)); + if (tip === 'mektup') belgeYazdir('Teklif Mektubu', teklifMektubuHtml(f, toplam), printWindow.current); + if (tip === 'cetvel') belgeYazdir('Birim Fiyat Teklif Cetveli', teklifCetveliHtml(f, satirlar, toplam), printWindow.current); + if (tip === 'liste') belgeYazdir('Evrak Kontrol Listesi', evrakListesiHtml(f, documents), printWindow.current); + printWindow.current = null; } catch (e) { - setHata(e.message); - } + printWindow.current?.close(); + printWindow.current = null; + if(mounted.current) setHata(e.message); + } finally { if(mounted.current) setPrinting(false); } }; return ( @@ -389,7 +408,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => {