diff --git a/docs/quality/uygulama-durumu.md b/docs/quality/uygulama-durumu.md index 37c1806..c71e56b 100644 --- a/docs/quality/uygulama-durumu.md +++ b/docs/quality/uygulama-durumu.md @@ -79,9 +79,17 @@ Tarayıcı kanıtı: [Yenileme sonrası beş kart](evidence/qa-kanban-after-relo - İki test seçilmeyen 1.000 TL cihazın 80 TL'lik seçili cetvele karışmamasını, eksik kaynak/eşleştirme ve kaybolan kısım engellerini doğrular. Tarayıcıda tek sentetik satır için boş seçim engeli, kısım eşleştirmesi ve seçili fiyat gösterimi doğrulandı: [kanıt](evidence/qa-lot-selection.txt). - Aksaray'ın gerçek 34 kısmının resmi dokümanıyla karşılaştırma ve çok kısımlı tarayıcı kabulü henüz yoktur. Götürü bedel tutarı kullanıcı tarafından seçilen kapsam için ayrıca girilir. +## Belge deposu ile hazırlık bağlantısı + +- Belge gereksinimleri hesaba ait gerçek depo dosyasına bağlanır. Dosya kimliği ve oluşturma/boyut/son kullanma bilgileri kontrol kaydında tutulur; dosya değişikliği yeniden kontrol gerektirir. +- Dosya listesi fiziksel dosyanın mevcut olup olmadığını bildirir; sunucu dosya yolu istemciye verilmez. Kayıp/erişilemeyen dosya veya ihale tarihinde geçersiz belge çıktı koşullarını karşılamaz. +- Hazırlık formunda dosya seçimi, indirme, liste yenileme ve yüklenme/hata durumları bulunur. Çıktı isteğinde dosyalar tekrar sunucudan kontrol edilir. Dosya türüne göre içerik/ÜTS/mevzuat doğrulaması yapılmış sayılmaz. +- Yeni HTTP testi gerçek baytlarla yükleme → listeleme → indirme → üst veri değiştirme → silme zincirini ve başka hesabın indirme isteğine 404'ü doğrular. Bir kural testi kayıp dosya, değişmiş bilgi, süresi dolmuş ve geçersiz tarih senaryolarını kapsar. +- Tarayıcı üzerinden dosya seçme/indirme ve son kontrol sonrası yazdırma davranışı, özellikle yeni pencere engeli, henüz doğrulanmalı. Bu çalışma D01/D07 belge bağlantısını ilerletir; OCR ve beş gerçek dosya kabulünü kapatmaz. + ## Doğrulama -`npm test`: 33/33 geçti (son yeniden çalıştırma: 6 Eylül 2026). +`npm test`: 35/35 geçti (son yeniden çalıştırma: 6 Eylül 2026). `npm run build`: geçti; mevcut büyük paket/dinamik import uyarıları sürüyor. `node --check server/index.js`, `node --check server/user-store.mjs`, `git diff --check`: geçti. diff --git a/server/user-store.mjs b/server/user-store.mjs index 1e07cca..ed05ace 100644 --- a/server/user-store.mjs +++ b/server/user-store.mjs @@ -250,11 +250,11 @@ export function addDocument(email, { ad, kategori, boyut, mime, dosya, sonKullan } export function listDocuments(email, { ikn } = {}) { - const alanlar = 'id, ad, kategori, boyut, mime, son_kullanma, ikn, olusturma'; + const alanlar = 'id, ad, kategori, boyut, mime, son_kullanma, ikn, olusturma, dosya'; 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=? ORDER BY olusturma DESC`).all(email) - ), []); + ).map(({dosya,...record}) => ({...record, erisilebilir: fs.existsSync(path.join(BELGE_DIR,dosya))})), []); } export function getDocument(email, id) { diff --git a/src/components/BidPackageBuilder.jsx b/src/components/BidPackageBuilder.jsx index 49e62a9..556fb54 100644 --- a/src/components/BidPackageBuilder.jsx +++ b/src/components/BidPackageBuilder.jsx @@ -1,4 +1,6 @@ -import React, { useState, useMemo } from 'react'; +import { listDocuments, downloadDocument } from '../services/account'; +import { tenderDateKey } from '../services/tenderCalendar'; +import React, { useState, useMemo, useEffect } from 'react'; import { bidRowTotal, validateBidRows, roundMoney, parseBidPrice } from '../services/bidValidation'; import { sourceRows, bidSourceSnapshot, restoreBidPrices } from '../services/bidSource'; import { rowLot, selectBidLots } from '../services/bidLots'; @@ -42,6 +44,15 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => { const { userProfile, mutateTender, savingCount, authUser, tenders } = useApp(); const tender = {...selectedTender, ...tenders.find(t => t.id === selectedTender?.id)}; const storedDraft = tender?.bidDraft || {}; + const [vault,setVault] = useState({loading:true,error:null,documents:[]}); + const [vaultRefresh,setVaultRefresh] = useState(0); + useEffect(() => { + let cancelled=false; + setVault({loading:true,error:null,documents:[]}); + listDocuments().then(data=>{if(!cancelled)setVault({loading:false,error:null,documents:data.belgeler || []});}) + .catch(error=>{if(!cancelled)setVault({loading:false,error:error.message,documents:[]});}); + return ()=>{cancelled=true;}; + },[authUser?.email,vaultRefresh]); const [dirty, setDirty] = useState(false); const [acik, setAcik] = useState(true); const [hata, setHata] = useState(null); @@ -101,7 +112,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => { const kalemEksik = teklifTuru === 'birim' && validateBidRows(satirlar).length > 0; const turCeliskisi = teklifTuru === 'goturu' && /birim fiyat/i.test(tender?.aciklama || ''); const sureEksik = !(Number(gecerlilikGun) > 0) || (kaynakGecerlilik && Number(gecerlilikGun) < Number(kaynakGecerlilik[1])); - const kontrolEksikleri = preparationBlockers(lotSelection.rows, documents, checks); + const kontrolEksikleri = preparationBlockers(lotSelection.rows, documents, checks, {...vault,deadlineDate:tenderDateKey(tender)}); const hazir = eksikler.length === 0 && toplam > 0 && !kalemEksik && !turCeliskisi && !sureEksik && !kaynakKontrolGerekli && kontrolEksikleri.length === 0 && lotSelection.errors.length === 0; const formVerisi = () => ({ @@ -115,10 +126,15 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => { ihaleTarihi: tender?.ihaleTarihi || tender?.tarih || '', }); - const uret = (tip) => { + const uret = async (tip) => { setHata(null); try { if (!hazir) throw new Error('Kalem, miktar, birim, fiyat, firma ve geçerlilik süresindeki eksikleri giderin.'); + const currentFiles = await listDocuments(); + 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)); @@ -332,7 +348,7 @@ export const BidPackageBuilder = ({ results, tender: selectedTender }) => { )} {setManualDocuments(value);setDirty(true);}} checks={checks} setChecks={setChecks}/> + setManualDocuments={value => {setManualDocuments(value);setDirty(true);}} checks={checks} setChecks={setChecks} vault={vault} refreshVault={()=>setVaultRefresh(n=>n+1)} downloadFile={(file)=>downloadDocument(file.id,file.ad).catch(e=>setHata(e.message))}/> {kontrolEksikleri.length > 0 &&
{kontrolEksikleri.length} belge/uygunluk kontrolü tamamlanmalı diff --git a/src/components/PreparationChecks.jsx b/src/components/PreparationChecks.jsx index ebbf7ff..f612574 100644 --- a/src/components/PreparationChecks.jsx +++ b/src/components/PreparationChecks.jsx @@ -1,11 +1,11 @@ import React, {useState} from 'react'; -import {HEALTH_CHECKS, documentKey} from '../services/preparationChecks'; +import {HEALTH_CHECKS, documentKey, documentRevision} from '../services/preparationChecks'; const fieldClass = 'w-full border border-slate-300 rounded-lg p-2 text-xs bg-white'; const Status = ({value, onChange, optional = true}) => ; -export function PreparationChecks({rows, documents, manualDocuments, setManualDocuments, checks, setChecks}) { +export function PreparationChecks({rows, documents, manualDocuments, setManualDocuments, checks, setChecks, vault, refreshVault, downloadFile}) { const [name,setName] = useState(''); const [clause,setClause] = useState(''); const updateDocument = (key, patch) => setChecks(old => ({...old,documents:{...old.documents,[key]:{...old.documents?.[key],...patch}}})); @@ -14,6 +14,10 @@ export function PreparationChecks({rows, documents, manualDocuments, setManualDo
Belge ve ürün uygunluğu

Her kontrol için belge adı/sürümü ve kaynak maddeyi yazın. ÜTS, numune ve servis koşulları her ihalede zorunlu değildir; uygulanmıyorsa gerekçesini belirtin. Bu kayıtlar kullanıcı kontrolüdür; resmi uygunluk onayı değildir.

+

Dosyaları Doküman Deposu'na yükleyin, ardından burada ilgili belge gereksinimine bağlayın.

+ + {vault.loading &&

Belge deposu yükleniyor…

} + {vault.error &&

Belge deposu yüklenemedi: {vault.error}

} {documents.map(d => { const key = documentKey(d), c = checks.documents?.[key] || {}; return
@@ -21,6 +25,17 @@ export function PreparationChecks({rows, documents, manualDocuments, setManualDo

{d.note}

{manualDocuments.some(item => documentKey(item) === key) && } + + {vault.documents.some(f=>f.id===c.documentId) && } updateDocument(key,{status})}/>