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(` ${kacir(baslik)}${govde}
`); w.document.close(); w.focus(); return w; } function ustBilgi(f) { return `
İdarenin adı: ${kacir(f.idare)}
İşin adı: ${kacir(f.isinAdi)}
İhale kayıt numarası: ${kacir(f.ikn)}
İhale tarihi: ${kacir(f.ihaleTarihi || '—')}
`; } /** KİK birim fiyat / götürü bedel teklif mektubu metni. */ export function teklifMektubuHtml(f, toplam) { const goturu = f.teklifTuru === 'goturu'; const tur = goturu ? 'GÖTÜRÜ BEDEL TEKLİF MEKTUBU' : 'BİRİM FİYAT TEKLİF MEKTUBU'; const bedelCumle = goturu ? 'işin tamamını teklif ettiğimiz toplam bedel karşılığında yapmayı' : 'işi, teklif birim fiyat cetvelinde belirttiğimiz birim fiyatlar üzerinden yapmayı'; return `

${tur}

${ustBilgi(f)}

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.)

İstekliye ait bilgiler

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)}
${kacir(f.yetkiliAd)}
${kacir(f.yetkiliGorev)}
Kaşe ve İmza

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 ` ${i + 1} ${kacir(r.name)} ${miktar ?? kacir(r.quantity || '—')} ${kacir(birim)} ${bf ? bf.toLocaleString('tr-TR',{minimumFractionDigits:2,maximumFractionDigits:8}) : '—'} ${tutar ? paraTR(tutar) : '—'} `; }).join(''); return `

BİRİM FİYAT TEKLİF CETVELİ

${ustBilgi(f)} ${govde}
SıraMal / hizmet adı MiktarBirim Birim fiyat (TL)Tutar (TL)
Toplam tutar (KDV hariç) ${paraTR(toplam)}

Yazı ile: ${kacir(yaziylaTL(toplam))}

${kacir(f.firmaUnvan)}
${kacir(f.yetkiliAd)} · ${kacir(f.yetkiliGorev)}
Kaşe ve İmza
`; } /** Teklif zarfına girecek evrakın kontrol listesi / dosya kapağı. */ export function evrakListesiHtml(f, belgeler) { const satirlar = belgeler.map((b) => `
  • ${kacir(b.document)} ${b.mandatory === false ? ' (isteğe bağlı)' : ''} ${b.note ? `
    ${kacir(b.note)}
    ` : ''}
  • `).join(''); return `

    TEKLİF DOSYASI EVRAK KONTROL LİSTESİ

    ${ustBilgi(f)}
    İstekli: ${kacir(f.firmaUnvan)}

    Zarfa konulacak belgeler

    Hazırlayan: ${kacir(f.yetkiliAd)}
    Tarih / İmza

    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.

    `; }