▶️ Hesaplama Araçları

# Kullanım Talimatları # Bu not defteri brüt ücretten başlayarak aylık ve yıllık # net ücret (çalışan kesintileri) ve opsiyonel olarak işveren maliyetlerini # hesaplar. Tüm oranlar, tavan/ tabanlar ve istisnalar 2025 yılına aittir. # # 1) "Arayüz" bölümünde: # - Brüt Ücret (Aylık) # - Sosyal Güvenlik Durumu: Düzenli / Emekli # - Engellilik Durumu: Yok / 1.-2.-3. Derece # - İşveren Payları ve Maliyetlerini Göster: Aç/Kapa # * Açık ise işveren indirimi: %4 genel indirim (varsayılan) ya da # %5 imalat sektörü indirimi seçiniz. # # 2) Geçerlilik ve Yuvarlama: # - Brüt ücret, 2025 aylık asgari ücret (26.005,50 TL) ve üzerinde olmalıdır. # - Tüm parasal çıktılar TL ve 2 ondalık (kuruş) gösterilir. # # 3) Çıktılar: # - "Hesaplama Sonuçları" bölümünde Aylık ve Yıllık tablolar (DataFrame) # - "Grafikler" bölümünde: Brüt vs Net (aylık), İşveren Maliyeti vs Brüt (opsiyonel) # - "Dışa Aktarım" butonu ile CSV’leri /content/ altına indirirsiniz. # ===== Setup ===== import math from dataclasses import dataclass from typing import Dict, Tuple, List, Literal import numpy as np import pandas as pd import matplotlib.pyplot as plt from IPython.display import display, HTML, clear_output import ipywidgets as widgets pd.set_option("display.float_format", lambda x: f"{x:,.2f}".replace(",", "_").replace(".", ",").replace("_", ".")) def tl(x: float) -> str: """Format a number in Turkish Lira with thousands separator and 2 decimals.""" return f"{x:,.2f}".replace(",", "_").replace(".", ",").replace("_", ".") # ========================= # CONSTANTS_2025 (Tek Nokta) # ========================= CONSTANTS_2025: Dict = { # --- Ücret Eşikleri (SGK Taban/Tavan) --- # TR: Aylık prime esas kazanç alt ve üst sınırı (01.01.2025-31.12.2025). "ssi_base_min": 26005.50, # SGK tabanı (aylık) "ssi_base_max": 195041.40, # SGK tavanı (aylık) # --- Asgari Ücret ve İstisnalar --- # TR: Asgari ücret üzerinden gelir ve damga vergisi istisnası 2025'te de uygulanır. # Aşağıdaki tutarlar ilgili ayda "hesaplanan vergiden mahsup edilecek azami istisna" değerleridir. # Gelir Vergisi istisnası ay bazında değişmektedir (tebliğ ve duyurulara göre). "min_wage_gross": 26005.50, "monthly_gv_exemption": { # Gelir Vergisi istisna tutarı (TL) - aylık, 2025 1: 3315.70, 2: 3315.70, 3: 3315.70, 4: 3315.70, 5: 3315.70, 6: 3315.70, 7: 3315.70, 8: 4257.57, 9: 4420.93, 10: 4420.93, 11: 4420.93, 12: 4420.93 }, "monthly_dv_exemption": {m: 197.38 for m in range(1, 13)}, # Damga Vergisi istisnası (TL) - aylık, sabit # --- Damga Vergisi --- # TR: Ücretlerde damga vergisi binde 7,59 (0,759%). "stamp_tax_rate": 0.00759, # --- Gelir Vergisi Dilimleri (Ücret Geliri İçin) --- # TR: 2025 dilimler (kümülatif yıllık matrah). # format: (üst_sınır, oran) "income_tax_brackets": [ (158000.00, 0.15), (330000.00, 0.20), (1200000.00, 0.27), (4300000.00, 0.35), (float("inf"), 0.40), ], # --- Çalışan Prim Oranları (Düzenli Çalışan) --- # TR: İşçi SGK %14, İşçi İşsizlik %1 (SGK matrahı üzerinden, taban/tavan uygulanır) "employee_ssi_rate": 0.14, "employee_unemp_rate": 0.01, # --- Emekli Çalışan (SGDP) Prim Oranları --- # TR: Emekli çalışan işçi payı %7,5; işveren toplam SGDP %24,75; işsizlik yok. "retiree_employee_sgdp_rate": 0.075, "retiree_employer_sgdp_total": 0.2475, # 22.5 + 2.25 = 24.75 # --- İşveren Prim Oranları (Düzenli Çalışan) --- # TR: Genel işveren SGK oranı %20,75; şartlı indirimle %4 puan -> %16,75; # İmalat sektörü için %5 puan -> %15,75. # İşveren işsizlik primi ayrıca %2 (indirime dahil değildir). "employer_ssi_rate_base": 0.2075, "employer_unemp_rate": 0.02, # İşveren indirim tanımları "employer_discounts": { "4pt": 0.04, # genel indirim "5pt_mfg": 0.05 # imalat sektörü }, # --- Engellilik İndirimi Tutarları (Aylık) --- # TR: Gelir vergisi matrahından aylık indirim (TL). "disability_allowances": { "none": 0.0, "level1": 9900.0, "level2": 5700.0, "level3": 2400.0 } } # ======================== # Core Payroll Functions # ======================== def clamp_ssi_base(gross: float, c=CONSTANTS_2025) -> float: """Clamp gross to SSI base range [min, max].""" return max(c["ssi_base_min"], min(gross, c["ssi_base_max"])) def calc_employee_contribs( gross: float, employee_status: Literal["regular", "retired"], c=CONSTANTS_2025 ) -> Dict[str, float]: """ Compute employee-side statutory deductions on SSI base. - Regular: SSI 14% + Unemployment 1% (on SSI base) - Retired: SGDP 7.5% (no unemployment) Returns dict with 'emp_ssi', 'emp_unemp', 'emp_total' """ # TR: SGK matrahı taban/tavan arasında sınırlandırılır ssi_base = clamp_ssi_base(gross, c) if employee_status == "retired": emp_ssi = ssi_base * c["retiree_employee_sgdp_rate"] emp_unemp = 0.0 else: emp_ssi = ssi_base * c["employee_ssi_rate"] emp_unemp = ssi_base * c["employee_unemp_rate"] return { "emp_ssi": round(emp_ssi, 2), "emp_unemp": round(emp_unemp, 2), "emp_total": round(emp_ssi + emp_unemp, 2) } def calc_taxable_base( gross: float, emp_contribs: Dict[str, float], disability_level: Literal["none", "level1", "level2", "level3"], c=CONSTANTS_2025 ) -> float: """ Compute monthly income tax base = gross - employee statutory deductions - disability allowance. (Stamp tax matrahı gross'tur; gelir vergisi matrahı bu formüle göredir.) """ base = gross - emp_contribs["emp_ssi"] - emp_contribs["emp_unemp"] base -= c["disability_allowances"][disability_level] return max(0.0, round(base, 2)) def _tax_due_for_cumulative_base(cum_base_before: float, month_base: float, brackets: List[Tuple[float, float]]) -> float: """ Compute tax for this month using progressive annual brackets with cumulative base. We tax the increment [cum_before, cum_before + month_base] across brackets. """ if month_base <= 0: return 0.0 tax = 0.0 start = cum_base_before end = cum_base_before + month_base prev_limit = 0.0 for limit, rate in brackets: # taxable portion within this bracket: lower = max(start, prev_limit) upper = min(end, limit) if upper > lower: tax += (upper - lower) * rate if end <= limit: break prev_limit = limit return round(tax, 2) def calc_income_tax_series( monthly_tax_bases: List[float], brackets: List[Tuple[float, float]], monthly_gv_exemptions: Dict[int, float] ) -> List[float]: """ Given 12 monthly tax bases and progressive brackets, compute monthly tax using cumulative progression, then subtract monthly minimum wage GV exemption (cannot exceed computed monthly tax). Returns list of final monthly income taxes after exemption. """ taxes = [] cum = 0.0 for i, base in enumerate(monthly_tax_bases, start=1): raw_tax = _tax_due_for_cumulative_base(cum, base, brackets) # TR: Asgari ücret GV istisnası - o ay hesaplanan vergiden mahsup; aşamaz gv_ex = monthly_gv_exemptions.get(i, 0.0) tax_after_ex = max(0.0, round(raw_tax - min(gv_ex, raw_tax), 2)) taxes.append(tax_after_ex) cum += base return taxes def calc_stamp_duty(gross: float, month: int, c=CONSTANTS_2025) -> float: """ Stamp duty = gross * 0.759%; then apply monthly exemption (cap at computed duty). """ raw = round(gross * c["stamp_tax_rate"], 2) dv_ex = c["monthly_dv_exemption"].get(month, 0.0) return max(0.0, round(raw - min(dv_ex, raw), 2)) def calc_employer_contribs( gross: float, employee_status: Literal["regular", "retired"], employer_discount: Literal["none", "4pt", "5pt_mfg"] = "4pt", c=CONSTANTS_2025 ) -> Dict[str, float]: """ Compute employer-side contributions on SSI base. - Regular: Employer SSI (base 20.75% less discount) + Employer Unemployment 2% - Retired: SGDP employer total 24.75% (no unemployment separate) """ ssi_base = clamp_ssi_base(gross, c) if employee_status == "retired": emp_ssi = ssi_base * c["retiree_employer_sgdp_total"] emp_unemp = 0.0 else: # TR: İndirim, işveren SGK oranından (20,75) puan olarak düşülür; işsizlik %2 ayrıca eklenir. discount_pt = 0.0 if employer_discount == "4pt": discount_pt = c["employer_discounts"]["4pt"] elif employer_discount == "5pt_mfg": discount_pt = c["employer_discounts"]["5pt_mfg"] employer_ssi_rate = max(0.0, c["employer_ssi_rate_base"] - discount_pt) emp_ssi = ssi_base * employer_ssi_rate emp_unemp = ssi_base * c["employer_unemp_rate"] return { "er_ssi": round(emp_ssi, 2), "er_unemp": round(emp_unemp, 2), "er_total": round(emp_ssi + emp_unemp, 2) } def calc_monthly( gross: float, employee_status: Literal["regular", "retired"], disability_level: Literal["none", "level1", "level2", "level3"], month: int, employer_view: bool, employer_discount: Literal["4pt", "5pt_mfg"] = "4pt", c=CONSTANTS_2025 ) -> Dict[str, float]: """ Orchestrate monthly calculation for the selected month (1..12). Returns dict with all components incl. net and employer totals. """ emp = calc_employee_contribs(gross, employee_status, c) tax_base = calc_taxable_base(gross, emp, disability_level, c) income_tax = calc_income_tax_series([tax_base], c["income_tax_brackets"], c["monthly_gv_exemption"])[0] stamp = calc_stamp_duty(gross, month, c) net = round(gross - emp["emp_total"] - income_tax - stamp, 2) out = { "Brüt Ücret": gross, "SGK İşçi": emp["emp_ssi"], "İşsizlik İşçi": emp["emp_unemp"], "Damga Vergisi": stamp, "Gelir Vergisi": income_tax, "Net Ücret": net } if employer_view: er = calc_employer_contribs(gross, employee_status, employer_discount, c) out.update({ "SGK İşveren": er["er_ssi"], "İşsizlik İşveren": er["er_unemp"], "Toplam İşveren Maliyeti": round(gross + er["er_total"], 2) }) return out def calc_annual( gross: float, employee_status: Literal["regular", "retired"], disability_level: Literal["none", "level1", "level2", "level3"], employer_view: bool, employer_discount: Literal["4pt", "5pt_mfg"] = "4pt", c=CONSTANTS_2025 ) -> pd.DataFrame: """ Build a 12-row DataFrame with cumulative-tax logic (income tax brackets). """ # TR: Aylık bazda önce işçi kesintileri ve GV matrahı çıkarılır; sonra kümülatif GV hesabı yapılır. months = list(range(1, 13)) emp_rows = [] tax_bases = [] for m in months: emp = calc_employee_contribs(gross, employee_status, c) base = calc_taxable_base(gross, emp, disability_level, c) tax_bases.append(base) stamp = calc_stamp_duty(gross, m, c) emp_rows.append((emp, base, stamp)) income_taxes = calc_income_tax_series(tax_bases, c["income_tax_brackets"], c["monthly_gv_exemption"]) records = [] for idx, m in enumerate(months): emp, base, stamp = emp_rows[idx] income_tax = income_taxes[idx] net = round(gross - emp["emp_total"] - income_tax - stamp, 2) row = { "Ay": m, "Brüt Ücret": gross, "SGK İşçi": emp["emp_ssi"], "İşsizlik İşçi": emp["emp_unemp"], "Damga Vergisi": stamp, "Gelir Vergisi": income_tax, "Net Ücret": net } if employer_view: er = calc_employer_contribs(gross, employee_status, employer_discount, c) row.update({ "SGK İşveren": er["er_ssi"], "İşsizlik İşveren": er["er_unemp"], "Toplam İşveren Maliyeti": round(gross + er["er_total"], 2) }) records.append(row) df = pd.DataFrame(records) # TR: Yıl toplam satırı totals = df.drop(columns=["Ay"]).sum(numeric_only=True) total_row = {"Ay": "Toplam"} total_row.update({k: round(v, 2) for k, v in totals.items()}) df = pd.concat([df, pd.DataFrame([total_row])], ignore_index=True) return df # ================ # Arayüz # ================ style = {"description_width": "160px"} layout = widgets.Layout(width="520px") gross_input = widgets.FloatText( value=CONSTANTS_2025["min_wage_gross"], description="Brüt Ücret (Aylık):", style=style, layout=layout ) gross_help = widgets.HTML("📌 En az asgari ücret kadar olmalıdır (2025: 26.005,50 TL).") status_dropdown = widgets.Dropdown( options=[("Düzenli Çalışan", "regular"), ("Emekli Çalışan", "retired")], value="regular", description="Sosyal Güvenlik Durumu:", style=style, layout=layout ) disability_dropdown = widgets.Dropdown( options=[("Yok", "none"), ("1. Derece", "level1"), ("2. Derece", "level2"), ("3. Derece", "level3")], value="none", description="Engellilik Durumu:", style=style, layout=layout ) employer_view_toggle = widgets.Checkbox( value=False, description="İşveren Payları ve Maliyetlerini Göster", indent=False ) discount_dropdown = widgets.Dropdown( options=[("%4 İndirim", "4pt"), ("%5 İmalat Sektörü İndirimi", "5pt_mfg")], value="4pt", description="İşveren İndirimi:", style=style, layout=layout ) discount_help = widgets.HTML("📌 İndirim işveren SGK oranından puan olarak düşülür; işsizlik (%2) ayrıca eklenir.") error_box = widgets.HTML("") export_btn = widgets.Button(description="Tabloyu Dışa Aktar (CSV)", button_style="", tooltip="Aylık ve Yıllık tabloları /content/ klasörüne kaydeder.") out_tables = widgets.Output() out_charts = widgets.Output() def validate_inputs() -> Tuple[bool, str]: # TR: Brüt ücret doğrulaması if gross_input.value is None or math.isnan(gross_input.value): return False, "Lütfen geçerli bir brüt ücret giriniz." if gross_input.value < CONSTANTS_2025["min_wage_gross"] - 0.005: return False, f"Brüt ücret asgari ücretten düşük olamaz (>= {tl(CONSTANTS_2025['min_wage_gross'])})." return True, "" def recompute(*args): ok, msg = validate_inputs() if not ok: error_box.value = f"{msg}" with out_tables: clear_output() with out_charts: clear_output() return error_box.value = "" gross = float(gross_input.value) employee_status = status_dropdown.value disability = disability_dropdown.value employer_view = employer_view_toggle.value discount = discount_dropdown.value month = 1 # TR: Aylık özet tablosu için Ocak ayı gösterilir; yıllık tabloda 12 ay detay verilir. # --- Aylık --- monthly = calc_monthly( gross=gross, employee_status=employee_status, disability_level=disability, month=month, employer_view=employer_view, employer_discount=discount ) monthly_df = pd.DataFrame([monthly]) # --- Yıllık --- annual_df = calc_annual( gross=gross, employee_status=employee_status, disability_level=disability, employer_view=employer_view, employer_discount=discount ) # --- Doğrulama (eşitlik kontrolü) --- # TR: Brüt = Net + İşçi SGK + İşçi İşsizlik + Damga Vergisi + Gelir Vergisi (aylık) lhs = round(monthly_df.loc[0, "Brüt Ücret"], 2) rhs = round( monthly_df.loc[0, "Net Ücret"] + monthly_df.loc[0, "SGK İşçi"] + monthly_df.loc[0, "İşsizlik İşçi"] + monthly_df.loc[0, "Damga Vergisi"] + monthly_df.loc[0, "Gelir Vergisi"], 2 ) equality_pass = abs(lhs - rhs) <= 0.02 # --- Tablo Gösterimleri --- with out_tables: clear_output() display(HTML("

🧮 Aylık Tablo (Ocak örneği)

")) display(monthly_df.style.format({col: tl for col in monthly_df.columns if col != "Ay"})) display(HTML("

📆 Yıllık Tablo (12 Ay, Kümülatif Vergi)

")) display(annual_df.style.format({col: tl for col in annual_df.columns if col != "Ay"})) msg_html = f"

Formül Doğrulaması: {'✅ Başarılı' if equality_pass else '❌ Hata'} — "\ f"Brüt ({tl(lhs)}) {'=' if equality_pass else '≠'} Net + kesintiler ({tl(rhs)}).

" display(HTML(msg_html)) # --- Grafikler --- with out_charts: clear_output() # Grafik 1: Brüt vs Net (aylık) plt.figure(figsize=(6, 4)) plt.bar(["Brüt", "Net"], [monthly_df.loc[0, "Brüt Ücret"], monthly_df.loc[0, "Net Ücret"]]) plt.title("Aylık Brüt vs Net") plt.ylabel("TL") plt.show() # Grafik 2: İşveren Maliyeti vs Brüt (opsiyonel) if employer_view: plt.figure(figsize=(6, 4)) total_cost = monthly_df.loc[0, "Toplam İşveren Maliyeti"] plt.bar(["Brüt", "İşveren Maliyeti"], [monthly_df.loc[0, "Brüt Ücret"], total_cost]) plt.title("Aylık İşveren Maliyeti (Brüt ile Karşılaştırma)") plt.ylabel("TL") plt.show() # Export button handler needs latest DFs in closure def do_export(_): monthly_path = "/content/aylik_tablo.csv" annual_path = "/content/yillik_tablo.csv" monthly_df.to_csv(monthly_path, index=False) annual_df.to_csv(annual_path, index=False) with out_tables: display(HTML(f"

💾 CSV dışa aktarıldı: " f"{monthly_path} ve {annual_path}

")) export_btn.on_click(do_export) # UI layout & bindings box_left = widgets.VBox([ gross_input, gross_help, status_dropdown, disability_dropdown, employer_view_toggle, discount_dropdown, discount_help, error_box, export_btn ]) display(HTML("

Arayüz

")) display(box_left) display(out_tables) display(out_charts) for w in (gross_input, status_dropdown, disability_dropdown, employer_view_toggle, discount_dropdown): w.observe(recompute, names="value") # İlk hesap recompute() # ====================== # Doğrulama Testleri # ====================== def _run_sanity_tests(): """Basic sanity tests required by the spec.""" results = [] def _case(gross, status, dis, employer_view): m = calc_monthly(gross, status, dis, month=1, employer_view=employer_view) # Identity property ok_identity = abs(m["Brüt Ücret"] - (m["Net Ücret"] + m["SGK İşçi"] + m["İşsizlik İşçi"] + m["Damga Vergisi"] + m["Gelir Vergisi"])) <= 0.02 return ok_identity, m # Low near minimum r1_ok, r1 = _case(CONSTANTS_2025["min_wage_gross"], "regular", "none", False) results.append(("Asgari ücret (düzenli, engel yok)", r1_ok)) # Median (örnek) r2_ok, r2 = _case(50000.0, "regular", "none", True) results.append(("Orta ücret + işveren görünümü", r2_ok)) # High above ceiling r3_ok, r3 = _case(250000.0, "regular", "none", True) results.append(("Tavan üzeri brüt (tavan uygulanmalı)", r3_ok)) # Disability levels impact m_none = calc_monthly(50000.0, "regular", "none", 1, False) m_l1 = calc_monthly(50000.0, "regular", "level1", 1, False) m_l2 = calc_monthly(50000.0, "regular", "level2", 1, False) m_l3 = calc_monthly(50000.0, "regular", "level3", 1, False) results.append(("Engellilik indirimi (matrah düşürür)", m_l1["Gelir Vergisi"] <= m_none["Gelir Vergisi"] and m_l2["Gelir Vergisi"] <= m_l1["Gelir Vergisi"] and m_l3["Gelir Vergisi"] <= m_l2["Gelir Vergisi"])) # Retired vs regular (işçi payları farklı) m_reg = calc_monthly(50000.0, "regular", "none", 1, False) m_ret = calc_monthly(50000.0, "retired", "none", 1, False) results.append(("Emekli çalışan işçi kesintileri farklı olmalı", m_ret["SGK İşçi"] < m_reg["SGK İşçi"] and m_ret["İşsizlik İşçi"] == 0.0)) # Employer discount affects only employer totals m4 = calc_monthly(50000.0, "regular", "none", 1, True, employer_discount="4pt") m5 = calc_monthly(50000.0, "regular", "none", 1, True, employer_discount="5pt_mfg") results.append(("İşveren indirimi net ücreti etkilemez", m4["Net Ücret"] == m5["Net Ücret"])) # Summary passed = sum(1 for _, ok in results if ok) total = len(results) return results, passed, total tests, passed, total = _run_sanity_tests() display(HTML("

Doğrulama Testleri

")) for name, ok in tests: display(HTML(f"• {name}: {'✅' if ok else '❌'}")) display(HTML(f"Toplam: {passed}/{total} geçti"))
Mobil sürümden çık