import { quantityParts, parseBidPrice, bidRowTotal, validateBidRows } from './bidValidation.js'; /** * İhale dosyası belgeleri — teklif mektubu, birim fiyat teklif cetveli ve * evrak kontrol listesi. * * NEDEN HTML/YAZDIR: jsPDF'in gömülü fontları WinAnsi; ş/ğ/ı/İ taşımıyor * (bkz. services/pdf.js, başlıkları ASCII'ye düşürüyor). Resmî teklif * mektubunda bozuk Türkçe kabul edilemez, o yüzden belgeler tam Türkçe HTML * olarak üretilip tarayıcının yazdır → "PDF olarak kaydet" akışına veriliyor. */ const BIRLER = ['', 'bir', 'iki', 'üç', 'dört', 'beş', 'altı', 'yedi', 'sekiz', 'dokuz']; const ONLAR = ['', 'on', 'yirmi', 'otuz', 'kırk', 'elli', 'altmış', 'yetmiş', 'seksen', 'doksan']; const BASAMAK = ['', 'bin', 'milyon', 'milyar', 'trilyon']; function ucBasamak(n) { const y = Math.floor(n / 100), o = Math.floor((n % 100) / 10), b = n % 10; let s = ''; if (y) s += y === 1 ? 'yüz' : BIRLER[y] + 'yüz'; if (o) s += ONLAR[o]; if (b) s += BIRLER[b]; return s; } /** 1850000.5 → "birmilyonsekizyüzellibin Türk Lirası elli Kuruş" */ export function yaziylaTL(n) { const sayi = Number(n); if (!isFinite(sayi)) return ''; const negatif = sayi < 0; const mutlak = Math.abs(sayi); const toplamKurus = Math.round((mutlak + Number.EPSILON) * 100); const tam = Math.floor(toplamKurus / 100); const kurus = toplamKurus % 100; const gruplar = []; let kalan = tam; while (kalan > 0) { gruplar.push(kalan % 1000); kalan = Math.floor(kalan / 1000); } let lira = ''; for (let i = gruplar.length - 1; i >= 0; i--) { const g = gruplar[i]; if (!g) continue; // "birbin" değil "bin" lira += (i === 1 && g === 1) ? 'bin' : ucBasamak(g) + BASAMAK[i]; } if (!lira) lira = 'sıfır'; let s = `${lira} Türk Lirası`; if (kurus > 0) s += ` ${ucBasamak(kurus)} Kuruş`; return (negatif ? 'eksi ' : '') + s; } export const paraTR = (n) => (Number(n) || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); /** "3 adet" → { miktar: 3, birim: "adet" } ; okunamazsa miktar null */ export const miktarAyristir = quantityParts; const kacir = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); const STIL = ` @page { size: A4; margin: 20mm 18mm; } * { box-sizing: border-box; } body { font-family: "Times New Roman", Georgia, serif; font-size: 11.5pt; line-height: 1.55; color: #000; } h1 { font-size: 13pt; text-align: center; margin: 0 0 4mm; letter-spacing: .3px; } h2 { font-size: 11pt; margin: 6mm 0 2mm; } .ust { font-size: 10pt; margin-bottom: 6mm; } .ust div { margin: .6mm 0; } table { width: 100%; border-collapse: collapse; margin: 3mm 0; font-size: 9.5pt; } th, td { border: 1px solid #444; padding: 1.6mm 2mm; text-align: left; vertical-align: top; } thead { display: table-header-group; } tr { break-inside: avoid; } th { background: #eee; font-weight: bold; } td.s, th.s { text-align: right; white-space: nowrap; } .toplam td { font-weight: bold; background: #f4f4f4; } .imza { margin-top: 12mm; width: 70mm; margin-left: auto; text-align: center; font-size: 10pt; } .imza .kutu { border: 1px solid #666; height: 26mm; margin-bottom: 2mm; } .not { font-size: 8.5pt; color: #555; margin-top: 8mm; } ul { margin: 2mm 0; padding-left: 6mm; } li { margin: 1.2mm 0; } .kutucuk { display: inline-block; width: 3.6mm; height: 3.6mm; border: 1px solid #333; margin-right: 2mm; vertical-align: -0.4mm; } @media print { .yazdirma { display: none; } } `; /** Belgeyi kendi penceresinde açıp yazdırma diyaloğunu tetikler. */ export function belgeYazdir(baslik, govde, existingWindow) { const w = existingWindow || window.open('', '_blank', 'width=900,height=1000'); if (!w) throw new Error('Tarayıcı yeni pencereyi engelledi. Açılır pencere iznini verip tekrar deneyin.'); if(w.closed) throw new Error('Çıktı penceresi kapatıldı. Yeniden deneyin.'); w.document.open(); w.document.write(`
Yukarıda ihale kayıt numarası ve adı yer alan ihaleye ilişkin ihale dokümanını oluşturan bütün belgeler tarafımızdan incelenmiş, okunmuş ve herhangi bir ayrım ve sınırlama yapılmadan bütün koşullarıyla kabul edilmiştir.
İhale dokümanında yer alan bütün şartları kabul ederek, söz konusu ${bedelCumle} kabul ve taahhüt ediyoruz.
Teklifimizin, ihale tarihinden itibaren ${kacir(f.gecerlilikGun || '[DOĞRULAYIN]')} takvim günü süreyle geçerli olduğunu beyan ediyoruz.
${f.kisim ? `Teklifimiz ${kacir(f.kisim)} kısmına/kısımlarına ilişkindir.
` : ''}Teklif ettiğimiz toplam bedel:
| Rakam ile | ${paraTR(toplam)} TL |
|---|---|
| Yazı ile | ${kacir(yaziylaTL(toplam))} |
(Teklif edilen bedele KDV dâhil değildir.)
| Adı / Ticaret unvanı | ${kacir(f.firmaUnvan)} |
|---|---|
| Tebligata esas adres | ${kacir(f.adres)} |
| Vergi dairesi / numarası | ${kacir(f.vergiDairesi)} / ${kacir(f.vergiNo)} |
| Ticaret sicil / MERSİS no | ${kacir(f.sicilNo)} |
| Telefon / E-posta | ${kacir(f.telefon)} · ${kacir(f.eposta)} |
Bu taslak Ihaleist tarafından hazırlanmıştır. İmzalamadan önce idarenin ihale dokümanındaki standart form metniyle karşılaştırınız.
`; } /** Birim fiyat teklif cetveli. */ export function teklifCetveliHtml(f, satirlar, toplam) { const errors = validateBidRows(satirlar); if (errors.length) throw new Error(errors.join(" ")); const govde = satirlar.map((r, i) => { const { miktar, birim } = miktarAyristir(r.quantity); const bf = parseBidPrice(r.birimFiyat); const tutar = bidRowTotal(r); return `| Sıra | Mal / hizmet adı | Miktar | Birim | Birim fiyat (TL) | Tutar (TL) |
|---|---|---|---|---|---|
| Toplam tutar (KDV hariç) | ${paraTR(toplam)} | ||||
Yazı ile: ${kacir(yaziylaTL(toplam))}
Liste, yüklediğiniz ihale dokümanının yapay zekâ analizinden üretilmiştir; zarfı kapatmadan önce idari şartnamenin yeterlik maddeleriyle karşılaştırınız.
`; }