feat: sinir deger hesaplayici — Tebliğ md. 79/45 formulleri, 2026 R katsayilari
Formuller kaynagindan alindi (mevzuat.gov.tr Kamu Ihale Genel Tebligi, kullanicinin Chrome'u uzerinden okundu; formuller Teblig'de gorsel oldugu icin ekran goruntusunden): - Md. 79.1.2 hizmet (personel dayali olmayan): SD = ((YM + ΣT)/(n+1)) × R, YM'nin %60 alti ve YM ustu teklifler haric - Md. 79.1.1 personel calistirmaya dayali: SD = kar haric YM - Md. 62/A.1 (RG 9/4/2026): mal aliminda 79 uygulanir - Md. 45.1.1 yapim: Tort1/σ/Tort2, C = Tort2/YM, K parcali, SD = K×Tort2/(C×N); N 1,00 (B/C/D/E gruplari) veya 1,20 (45.1.1.1) - R katsayilari: KIK Kurul karari 21.01.2026 (.doc'tan okundu): arac kiralama 0,77, bilgisayar 0,87, yemek 0,77, muhendislik 0,72, sigorta 0,74, tasima 0,78, diger 0,78 Dogrulama: Teblig'in kendi 45.2 ornegi (YM 100.000, N 1,20, 12 teklif) ile birebir — Tort1 66.270,00, σ 20.317,05, Tort2 63.237,50, C 0,632, K 0,627, SD 52.281,00. Yuvarlama kurallari ornekteki gibi (2/3 ondalik). Arac: ust menude "Sinir Deger"; alim turu, YM, teklif listesi, R/N secimi, "benim teklifim sinirin altinda mi" uyarisi, adim adim hesap. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { Scale, AlertTriangle, CheckCircle2, Info } from 'lucide-react';
|
||||
import { sinirDegerHizmet, sinirDegerPersonel, sinirDegerYapim, teklifleriAyir, R_2026, R_KAYNAK } from '../../services/sinirDeger';
|
||||
|
||||
/**
|
||||
* Aşırı düşük teklif sınır değeri hesaplayıcı.
|
||||
* Formüller Kamu İhale Genel Tebliği md. 79 (hizmet/mal) ve 45 (yapım),
|
||||
* R katsayıları KİK 21.01.2026 Kurul kararı — kaynaklar services/sinirDeger.js'de.
|
||||
* Teklifçinin sorusu: "Teklifim sınırın altında kalıp aşırı düşük sorgusuna girer mi?"
|
||||
*/
|
||||
const tl = (n) => (Number.isFinite(n) ? n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' TL' : '—');
|
||||
|
||||
export const SinirDeger = () => {
|
||||
const [tur, setTur] = useState('hizmet'); // hizmet | mal | personel | yapim
|
||||
const [ym, setYm] = useState('');
|
||||
const [tekliflerMetin, setTekliflerMetin] = useState('');
|
||||
const [rSecim, setRSecim] = useState(String(R_2026[6].r));
|
||||
const [rOzel, setROzel] = useState('');
|
||||
const [nKat, setNKat] = useState('1.2');
|
||||
const [benimTeklif, setBenimTeklif] = useState('');
|
||||
|
||||
const teklifler = useMemo(() => teklifleriAyir(tekliflerMetin), [tekliflerMetin]);
|
||||
const r = rSecim === 'ozel' ? Number(String(rOzel).replace(',', '.')) : Number(rSecim);
|
||||
|
||||
const sonuc = useMemo(() => {
|
||||
if (tur === 'personel') return ym ? sinirDegerPersonel({ karHaricYm: ym }) : null;
|
||||
if (!ym || !teklifler.length) return null;
|
||||
if (tur === 'yapim') return sinirDegerYapim({ ym, teklifler, n: Number(nKat) });
|
||||
return sinirDegerHizmet({ ym, teklifler, r });
|
||||
}, [tur, ym, teklifler, r, nKat]);
|
||||
|
||||
const benim = teklifleriAyir(benimTeklif)[0];
|
||||
const altinda = sonuc?.sd != null && Number.isFinite(benim) ? benim < sonuc.sd : null;
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<div className="bg-linear-to-r from-slate-900 via-emerald-950 to-teal-900 text-white p-6 rounded-2xl shadow-lg border border-emerald-800/40">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-2xl bg-emerald-600 flex items-center justify-center shadow-md"><Scale className="w-6 h-6" /></div>
|
||||
<div>
|
||||
<h2 className="font-extrabold text-xl">Sınır Değer Hesaplayıcı</h2>
|
||||
<p className="text-xs text-emerald-200 mt-0.5">Teklifiniz aşırı düşük teklif sorgusuna girer mi? Kamu İhale Genel Tebliği md. 79 / 45 formülleriyle.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="bg-white rounded-2xl border border-slate-200 shadow-xs p-5 space-y-4">
|
||||
<div>
|
||||
<span className="text-[11px] font-bold text-slate-600 block mb-1.5">Alım türü</span>
|
||||
<div className="grid grid-cols-2 gap-1.5">
|
||||
{[['hizmet', 'Hizmet (personel dayalı olmayan)'], ['mal', 'Mal alımı'], ['personel', 'Hizmet (personel çalıştırmaya dayalı)'], ['yapim', 'Yapım işi']].map(([k, ad]) => (
|
||||
<button key={k} type="button" onClick={() => setTur(k)}
|
||||
className={`text-xs font-bold px-3 py-2 rounded-lg border text-left ${tur === k ? 'bg-emerald-600 text-white border-emerald-600' : 'bg-white text-slate-700 border-slate-300 hover:bg-slate-50'}`}>
|
||||
{ad}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{tur === 'mal' && <p className="text-[11px] text-slate-500 mt-1.5">Md. 62/A.1 (RG 9/4/2026): mal alımında aşırı düşük değerlendirmesi 79 uncu maddeye göre yapılır — hizmet formülü uygulanır.</p>}
|
||||
</div>
|
||||
|
||||
<label className="block">
|
||||
<span className="text-[11px] font-bold text-slate-600 block mb-1">{tur === 'personel' ? 'Kâr hariç yaklaşık maliyet (TL)' : 'Yaklaşık maliyet — YM (TL)'}</span>
|
||||
<input value={ym} onChange={(e) => setYm(e.target.value)} placeholder="Örn. 1.000.000,00"
|
||||
className="w-full text-sm px-3 py-2 rounded-lg border border-slate-300 focus:ring-2 focus:ring-emerald-500 outline-none" />
|
||||
<span className="text-[10px] text-slate-400">İhale komisyonu yaklaşık maliyeti ilk oturumda açıklar; teklif öncesi tahminle deneyebilirsiniz.</span>
|
||||
</label>
|
||||
|
||||
{tur !== 'personel' && (
|
||||
<label className="block">
|
||||
<span className="text-[11px] font-bold text-slate-600 block mb-1">Geçerli teklifler (her satıra bir tutar)</span>
|
||||
<textarea value={tekliflerMetin} onChange={(e) => setTekliflerMetin(e.target.value)} rows={6}
|
||||
placeholder={'850.000\n900.000\n920.000\n…'}
|
||||
className="w-full text-sm px-3 py-2 rounded-lg border border-slate-300 font-mono focus:ring-2 focus:ring-emerald-500 outline-none" />
|
||||
<span className="text-[10px] text-slate-400">{teklifler.length} teklif okundu · teklif mektubu ve geçici teminatı usulüne uygun olanlar</span>
|
||||
</label>
|
||||
)}
|
||||
|
||||
{(tur === 'hizmet' || tur === 'mal') && (
|
||||
<div>
|
||||
<span className="text-[11px] font-bold text-slate-600 block mb-1">R katsayısı (2026)</span>
|
||||
<select value={rSecim} onChange={(e) => setRSecim(e.target.value)} className="w-full text-xs px-3 py-2 rounded-lg border border-slate-300 bg-white">
|
||||
{R_2026.map((x) => <option key={x.ad} value={String(x.r)}>{x.ad} — {x.r.toLocaleString('tr-TR')}</option>)}
|
||||
<option value="ozel">Özel değer gir…</option>
|
||||
</select>
|
||||
{rSecim === 'ozel' && (
|
||||
<input value={rOzel} onChange={(e) => setROzel(e.target.value)} placeholder="0,78"
|
||||
className="mt-1.5 w-32 text-sm px-3 py-1.5 rounded-lg border border-slate-300" />
|
||||
)}
|
||||
<p className="text-[10px] text-slate-400 mt-1">{R_KAYNAK}. Birden fazla hizmet konusu varsa YM içinde ağırlıklı olana göre.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tur === 'yapim' && (
|
||||
<div>
|
||||
<span className="text-[11px] font-bold text-slate-600 block mb-1">Sınır değer katsayısı N (md. 45.1.1.1)</span>
|
||||
<select value={nKat} onChange={(e) => setNKat(e.target.value)} className="w-full text-xs px-3 py-2 rounded-lg border border-slate-300 bg-white">
|
||||
<option value="1">1,00 — B üstyapı (bina), C sıhhi/mekanik tesisat, D elektrik, E elektronik-iletişim işleri</option>
|
||||
<option value="1.2">1,20 — diğer yapım işleri</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="block">
|
||||
<span className="text-[11px] font-bold text-slate-600 block mb-1">Sizin teklifiniz (TL) — isteğe bağlı</span>
|
||||
<input value={benimTeklif} onChange={(e) => setBenimTeklif(e.target.value)} placeholder="Örn. 780.000"
|
||||
className="w-full text-sm px-3 py-2 rounded-lg border border-slate-300 focus:ring-2 focus:ring-emerald-500 outline-none" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{!sonuc && (
|
||||
<div className="bg-slate-50 border border-dashed border-slate-300 rounded-2xl p-8 text-center text-xs text-slate-500">
|
||||
Yaklaşık maliyeti ve teklifleri girin; sınır değer ve adımları burada görünür.
|
||||
</div>
|
||||
)}
|
||||
{sonuc?.hata && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-2xl p-4 flex gap-2 text-xs text-red-800"><AlertTriangle className="w-4 h-4 shrink-0" />{sonuc.hata}</div>
|
||||
)}
|
||||
{sonuc && !sonuc.hata && (
|
||||
<>
|
||||
<div className="bg-slate-900 text-white rounded-2xl p-5 space-y-2">
|
||||
<p className="text-[11px] font-bold text-slate-300 uppercase tracking-wider">Aşırı düşük teklif sınır değeri</p>
|
||||
<p className="text-3xl font-black text-emerald-300">{tl(sonuc.sd)}</p>
|
||||
{altinda != null && (
|
||||
<div className={`mt-2 rounded-xl p-3 text-xs flex gap-2 ${altinda ? 'bg-amber-500/20 border border-amber-400/40 text-amber-100' : 'bg-emerald-500/20 border border-emerald-400/40 text-emerald-100'}`}>
|
||||
{altinda ? <AlertTriangle className="w-4 h-4 shrink-0" /> : <CheckCircle2 className="w-4 h-4 shrink-0" />}
|
||||
<span>
|
||||
Teklifiniz <strong>{tl(benim)}</strong> — sınırın {altinda ? <><strong>altında</strong>: aşırı düşük teklif açıklaması istenir (md. 79.2 / 45.1.2), maliyet bileşenlerini belgeyle savunmaya hazır olun.</> : <><strong>üstünde</strong>: aşırı düşük sorgusuna girmez.</>}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-white rounded-2xl border border-slate-200 shadow-xs p-5 space-y-2">
|
||||
<h3 className="font-bold text-sm text-slate-900">Hesap adımları</h3>
|
||||
<ol className="space-y-1.5 text-xs text-slate-700 list-decimal pl-5">
|
||||
{sonuc.adimlar.map((a, i) => <li key={i} className="leading-relaxed">{a}</li>)}
|
||||
</ol>
|
||||
{sonuc.haric?.length > 0 && (
|
||||
<p className="text-[11px] text-slate-500">Hesap dışı bırakılan teklifler: {sonuc.haric.map((x) => x.toLocaleString('tr-TR')).join(', ')}</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-2xl p-4 flex gap-2.5 text-[11px] text-blue-900 leading-relaxed">
|
||||
<Info className="w-4 h-4 shrink-0 mt-0.5" />
|
||||
<span>
|
||||
Formüller Kamu İhale Genel Tebliği md. 79.1.1–79.1.2 (hizmet), 62/A.1 (mal) ve 45.1.1 (yapım) metninden alındı; yapım hesabı Tebliğ'in 45.2 örneğiyle (YM 100.000, N 1,20 → 52.281,00 TL) birebir doğrulandı.
|
||||
Nihai sınır değeri ihale komisyonu tespit eder; bu hesap teklif stratejisi içindir.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user