İlk girişinizde şifrenizi değiştirmeniz gerekiyor.
Hoş geldiniz
Karşıt inceleme tutanaklarınızı buradan takip edin.
Karşıt İnceleme Tutanakları
Firmamız
Karşı Firma (Mükellef)
Sayı
Tutanak
Tebliğ
Son Tarih
Kalan
Durum
Cevap süresi: tebliğ tarihinden itibaren 1 ay (son tarih otomatik hesaplanır, tutanak ekranından değiştirilebilir). Tebliğ tarihi el ile girilir.
⏱ Yaklaşan Son Tarihler
📈 Son 6 Ay
Firmalarımız
Yeni firma eklediğinizde aynı ekranlar o firma için de çalışır; kurgu Atasan ile birebir aynıdır. "Kopyala" mevcut firmanın bilgilerini şablon olarak alır.
Belge Kontrol Panosu
firma × yıl özeti — boş hücreye tıklayıp yükleyin
Firma
Yıllar
Cari Hesap Planı
Muavin Döküm
KDV Beyan.
Muhtasar Bey.
Kurumlar Bey.
Defter Dökümü
Durum
Belge Arşivi — Detay
firma × yıl — bir kez yükleyin, tüm tutanaklarda kullanılsın
Her firma için yıl yıl: Cari Hesap Planı, Muavin Döküm, KDV beyannameleri (12 ay + önceki dönem Aralık), Muhtasar beyannameleri ve Kurumlar Vergisi Beyannamesi. Yüklenen belge yeşil tik ile işaretlenir; aynı yıla ait yeni bir karşıt inceleme geldiğinde tekrar yüklemeniz gerekmez.
İstatistikler
Grafikler
Firma bazında tutanak sayısıTutanağı düzenleyen YMM'ye göreAylara göre gelen tutanaklar (tutanak tarihi)Durum dağılımı
Tutanak Listesi
Firmamız ⇅
Karşı Firma ⇅
Sayı ⇅
YMM ⇅
Tutanak Tarihi ⇅
Tebliğ ⇅
Son Tarih ⇅
Dönem
Durum ⇅
e-Defter Mükellefiyeti ve Berat Takvimi
1) Firma bazında e-defter durumu
Firma e-defter mükellefi ise, tutanağın yasal defterler bölümünde kâğıt defter yerine e-defter dönemleri listelenir ve berat süresi henüz dolmamış dönemlere "Süresi gelmediğinden verilmemiştir." yazılır.
Toplu işlem:
Firma
e-Defter mükellefi mi?
Berat yükleme tercihi
Dönem sayısı
2) Berat yükleme son tarihleri
Tarihler mevzuattaki genel kurala göre önceden doldurulur (ilgili ayı takip eden 4. ayın 14'ü; Aralık için beyanname ayını takip eden ayın 14'ü). Uzatma (sirküler) varsa aşağıdaki hücreleri elle düzeltin — girdiğiniz tarihler kaydedilir ve tüm firmalarda kullanılır.
Ay
Berat son tarihi
Ay
Berat son tarihi
Geçici vergi dönemi
Berat son tarihi
Kullanıcılar
Her kullanıcı yalnızca kendi eklediği firmaları ve onlara ait
tutanakları görür. Yönetici tüm kullanıcıları yönetebilir ve firmaları devredebilir. Yeni kullanıcı ilk girişinde
şifresini değiştirmek zorundadır.
Ad Soyad
E-posta
Rol
Durum
Firma
Tutanak
Son giriş
Yeni Kullanıcı
Firma Devri
Bir firmayı tutanakları ve arşiviyle birlikte başka kullanıcıya aktarır.
→
Son İşlemler
Zaman
Kullanıcı
Eylem
Ayrıntı
IP
Veri Kaydetme / Yükleme
Tarayıcı uygulaması verileri bellekte tutar. Çalışmanızı JSON dosyası olarak kaydedin; programı tekrar açtığınızda bu dosyayı yükleyerek kaldığınız yerden devam edin.
Belge Klasörü (bilgisayarınızda)
Beyanname PDF'leri, muavin ve cari plan dosyaları sunucuya gönderilmez;
bilgisayarınızda kalır. Bir klasör bağlarsanız yüklediğiniz belgeler oraya da yazılır ve tarayıcı temizliğinden etkilenmez.
Klasör seçimi kaydedilir, her açılışta yeniden seçmeniz gerekmez.
"Dosyaya bağlan": Chrome/Edge'de bir kayıt dosyası seçersiniz; her değişiklik otomatik oraya yazılır (tekrar indirme gerekmez).
1 · Tutanak PDF2 · Bilgiler & Süre3 · Luca Dosyaları4 · Kontrol & Düzenleme5 · Excel İndir
1) Sistemden Gelen Karşıt İnceleme Formu (PDF)
Karşıt İnceleme Tutanağı PDF'ini buraya bırakınveya tıklayıp seçin — sayı, tarih, karşı firma, dönem ve fatura listesi otomatik okunur
2) Tutanak Bilgileri ve Süre Takibi
3) Luca'dan Alınan Exceller
Cari Hesap Plan ListesiLuca → cari hesap planı (.xlsx)
Muavin DökümLütfen ilgili yıla ait muavin döküm yükleyiniz (.xlsx)
4a) Mükellefin Karşıt İncelemeye Konu Faturaları satışlarımız → karşı firmaya
Fatura Tarihi
Seri
Numara
Tutar (TL)
KDV (TL)
Defter Kayıt
Yevmiye
Ödeme Şekli
Kontrol (program notu)
4b) İlgili Dönemde Satılan Malların Tedarik Edildiği Firmalar en yüksek 10 alış faturası — belge bazında, aynı firma tekrar edebilir
191 İndirilecek KDV'den en yüksek KDV'li alışlar bulunur, matrah hesaplanır ve 15'li grup / diğer hesaplar ile çapraz kontrol edilir. Duran varlık (taşıt, demirbaş) çıkan alımların işareti otomatik kaldırılır — "satılan mal" kapsamına girmediği için. İlk seçili 10 satır Excel'e yazılır; kutucuklarla değiştirebilirsiniz.
Ünvan
VKN
Vergi Dairesi
Fatura Tarihi
Seri
Numara
KDV Dahil
Kontrol (program notu)
4c) Önceki Dönem ve İlgili Dönem KDV Beyan Bilgileri KDV1 beyanname + tahakkuk PDF'lerinden
Beyannameler firma arşivinde saklanır — bir dahaki tutanakta yeniden yüklemeniz gerekmez. Aynı aya ait birden fazla beyanname varsa (düzeltme) onay zamanı en yeni olan kullanılır. Tahakkuk fiş numarası tahakkuk PDF'inden alınır.
1) PDF YükleBeyanname + tahakkuk PDF'lerini (toplu) buraya bırakın
2) Klasörden OkuBilgisayarınızdaki beyanname klasörünü seçin — içindeki tüm PDF'ler taranır (Chrome/Edge)
3) E-Beyanname BağlantısıBekleniyor — dijital vergi dairesinden otomatik indirme ileride eklenecek
Dönem
Teslim ve Hizmet Bedeli
Özel Matrah Dahil Olm.
KDV Matrahı
Hesaplanan KDV
İlave KDV
Toplam KDV
İndirimler Toplamı
Ödenmesi Gereken
Devreden KDV
Tahakkuk Fiş No
Durum
4c) Mükellefin Yasal Defterlere İlişkin Bilgileri tutanak 2. bölüm
Luca Defter Döküm Listesi (.xls)bu dosya tüm firmaları kapsar — yılda bir kez yüklemeniz yeterli
Defterin Nev'i
Başlangıç
Bitiş
Tasdik Makamı
Tasdik Tarihi
Tasdik / Berat No
Açıklama
Defter Türü Açıklama
4d) Muhtasar Beyannamesinde Yer Alan Çalışan Kişi Sayıları MUHSGK beyanname PDF'lerinden
Her ayın Muhtasar ve Prim Hizmet Beyannamesindeki "VERGİ BİLDİRİMİ" tablosunun "Toplam Çalışan Sayısı" sütunu okunur. Beyannameler firma arşivinde saklanır; düzeltme varsa onay zamanı en yeni olan geçerlidir.
1) PDF YükleMuhtasar beyanname PDF'lerini (toplu) buraya bırakın
2) Klasörden OkuMuhtasar beyanname klasörünü seçin — içindeki tüm PDF'ler taranır (Chrome/Edge)
3) E-Beyanname BağlantısıBekleniyor — dijital vergi dairesinden otomatik indirme ileride eklenecek
Dönem
Çalışan İşçi Sayısı
Beyannamenin Verildiği Vergi Dairesi
Durum
4e) Mükellefin Ortaklık Bilgileri kurumlar vergisi beyannamesinden
İlgili yılın Kurumlar Vergisi Beyannamesindeki "KURUM ORTAKLARINA VE YÖNETİM KURULU ÜYELERİNE İLİŞKİN LİSTE" okunur. Beyanname firma arşivinde yıl bazında saklanır — bir kez yüklemeniz yeterli. İl, ikametgâh adresinden çıkarılır; vergi dairesi beyannamede yer almadığından elle doldurulur.
Kurumlar Vergisi Beyannamesi (PDF)ilgili yıla ait beyannameyi bırakın
Adı Soyadı/Ünvanı
Vergi/T.C. Kimlik No
Bağlı Olduğu İl
Bağlı Olduğu Vergi Dairesi
Pay Oranı
Açıklama
Beyannamedeki görev / adres
5) Excel Çıktıları
Resmi dosyalar sistem şablonuyla birebir aynı formatta üretilir (kontrol notları resmi dosyaya yazılmaz). Dosya adındaki tarih = cevap son tarihi.
{
t.ortaklar = t.ortaklar || []; t.muhOverride = t.muhOverride || {}; t.muhVd = t.muhVd || {};
t.pdfFaturalar = t.pdfFaturalar || []; t.satis = t.satis || []; t.alis = t.alis || [];
t.defterEk = t.defterEk || []; t.beratNo = t.beratNo || {};
t.durum = t.durum || "Bekliyor";
});
for (const fid in state.arsiv) { state.arsiv[fid].muh = state.arsiv[fid].muh || {}; }
state.defterDokum = state.defterDokum || {};
edef();
}
/* firma arşivi */
function A(firmaId) {
state.arsiv = state.arsiv || {};
const a = state.arsiv[firmaId] = state.arsiv[firmaId] || { kdv: {}, yillar: {} };
a.kdv = a.kdv || {}; a.yillar = a.yillar || {};
return a;
}
function AY(firmaId, yil) {
const a = A(firmaId);
return a.yillar[yil] = a.yillar[yil] || {};
}
const yilOf = t => (t && t.donemBas ? t.donemBas.split(".")[1] : "") || (t && t.donemBit ? t.donemBit.split(".")[1] : "");
let cur = null; // açık tutanak id
let filter = "all"; // ana sayfa filtresi
let fsHandle = null; // File System Access kayıt dosyası
const $ = id => document.getElementById(id);
/* ================================================================
SUNUCU + YEREL DEPO KATMANI
· Sunucuda yalnız çıkarılmış değerler durur (firma-yıl başına ~10 KB)
· PDF'ler, muavin ve cari plan kullanıcının kendi bilgisayarında:
tarayıcı veritabanı (IndexedDB) + isteğe bağlı kalıcı klasör bağlantısı
================================================================ */
let KULLANICI = null; // {id, ad, eposta, rol}
let YEREL_MOD = false; // sunucu yoksa (tek dosya / çevrimdışı kullanım)
let SUNUCU_HATA = 0;
let esitlemeZaman = null;
async function api(uc, gövde) {
const ayar = { headers: { "X-QK": "1" }, credentials: "same-origin" };
if (gövde !== undefined) {
ayar.method = "POST";
ayar.headers["Content-Type"] = "application/json";
ayar.body = JSON.stringify(gövde);
}
let r;
try { r = await fetch("api/api.php?p=" + uc, ayar); }
catch (err) { const e = new Error("Sunucuya ulaşılamadı"); e.yerel = true; throw e; }
let j = {};
try { j = await r.json(); } catch (e) {}
if (r.status === 401 && uc !== "ben") { location.href = "giris.html"; throw new Error("Oturum bitti"); }
if (!r.ok) throw new Error(j.hata || ("Sunucu hatası (" + r.status + ")"));
return j;
}
/* ---------------- IndexedDB: ağır dosyalar ---------------- */
const LDB = {
_db: null,
async ac() {
if (this._db) return this._db;
this._db = await new Promise((cz, rd) => {
const q = indexedDB.open("quantumkit", 1);
q.onupgradeneeded = () => {
const d = q.result;
if (!d.objectStoreNames.contains("dosya")) d.createObjectStore("dosya");
if (!d.objectStoreNames.contains("ayar")) d.createObjectStore("ayar");
};
q.onsuccess = () => cz(q.result);
q.onerror = () => rd(q.error);
});
return this._db;
},
async yaz(depo, anahtar, deger) {
const d = await this.ac();
return new Promise((cz, rd) => {
const t = d.transaction(depo, "readwrite");
t.objectStore(depo).put(deger, anahtar);
t.oncomplete = cz; t.onerror = () => rd(t.error);
});
},
async oku(depo, anahtar) {
const d = await this.ac();
return new Promise((cz, rd) => {
const q = d.transaction(depo, "readonly").objectStore(depo).get(anahtar);
q.onsuccess = () => cz(q.result); q.onerror = () => rd(q.error);
});
},
async sil(depo, anahtar) {
const d = await this.ac();
return new Promise((cz) => {
const t = d.transaction(depo, "readwrite");
t.objectStore(depo).delete(anahtar); t.oncomplete = cz;
});
},
async anahtarlar(depo) {
const d = await this.ac();
return new Promise((cz, rd) => {
const q = d.transaction(depo, "readonly").objectStore(depo).getAllKeys();
q.onsuccess = () => cz(q.result || []); q.onerror = () => rd(q.error);
});
},
async boyut() {
if (!navigator.storage || !navigator.storage.estimate) return null;
const e = await navigator.storage.estimate();
return { kullanilan: e.usage || 0, kota: e.quota || 0 };
}
};
/* ---------------- kalıcı klasör bağlantısı ---------------- */
let KLASOR = null;
async function klasorYukle() {
try {
KLASOR = await LDB.oku("ayar", "klasor");
if (KLASOR) {
const izin = await KLASOR.queryPermission({ mode: "readwrite" });
if (izin !== "granted") KLASOR.izinGerekli = true;
}
} catch (e) { KLASOR = null; }
klasorGoster();
}
async function klasorSec() {
if (!window.showDirectoryPicker) { toast("Klasör bağlama Chrome/Edge gerektirir"); return; }
try {
const h = await window.showDirectoryPicker({ mode: "readwrite" });
KLASOR = h;
await LDB.yaz("ayar", "klasor", h);
klasorGoster(); toast("Klasör bağlandı — bir daha seçmeniz gerekmeyecek");
} catch (e) {}
}
async function klasorIzin() {
if (!KLASOR) return klasorSec();
const i = await KLASOR.requestPermission({ mode: "readwrite" });
if (i === "granted") { delete KLASOR.izinGerekli; klasorGoster(); toast("Klasöre erişim açıldı"); }
else toast("İzin verilmedi");
}
async function klasorKaldir() {
KLASOR = null; await LDB.sil("ayar", "klasor"); klasorGoster(); toast("Klasör bağlantısı kaldırıldı");
}
function klasorGoster() {
const el = $("klasorDurum"); if (!el) return;
if (!KLASOR) {
el.innerHTML = `
Klasör bağlı değil. Beyanname ve muavin dosyalarınız yalnızca tarayıcı
veritabanında saklanıyor. Bir klasör bağlarsanız dosyalar bilgisayarınızda kalıcı olur ve tarayıcı temizliğinden etkilenmez.
`;
} else if (KLASOR.izinGerekli) {
el.innerHTML = `
📁 ${esc(KLASOR.name)} klasörü kayıtlı ancak bu oturumda erişim izni gerekiyor.
`;
} else {
el.innerHTML = `
📁 Bağlı klasör: ${esc(KLASOR.name)} — yüklenen belgeler buraya yazılır.
`;
}
}
/* dosyayı bağlı klasöre yaz (varsa) */
async function klasoreYaz(altKlasor, dosyaAdi, base64) {
if (!KLASOR || KLASOR.izinGerekli) return false;
try {
let d = KLASOR;
for (const p of altKlasor.split("/").filter(Boolean)) d = await d.getDirectoryHandle(p, { create: true });
const f = await d.getFileHandle(dosyaAdi, { create: true });
const w = await f.createWritable();
const bin = atob(base64); const u = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) u[i] = bin.charCodeAt(i);
await w.write(u); await w.close();
return true;
} catch (e) { console.warn("klasöre yazılamadı", e); return false; }
}
/* ---------------- ağır veri: kaydet / getir ---------------- */
async function agirYaz(anahtar, deger) { try { await LDB.yaz("dosya", anahtar, deger); } catch (e) { console.warn(e); } }
async function agirOku(anahtar) { try { return await LDB.oku("dosya", anahtar); } catch (e) { return null; } }
/* ---------------- sunucuya gönderilecek hafif kopya ---------------- */
function sunucuKopyasi() {
const s = JSON.parse(JSON.stringify({
firmalar: state.firmalar, tutanaklar: state.tutanaklar,
arsiv: state.arsiv || {}, defterDokum: state.defterDokum || {}, edefter: state.edefter || {}
}));
for (const fid in s.arsiv) {
const a = s.arsiv[fid];
for (const d in (a.kdv || {})) {
if (a.kdv[d].bey) delete a.kdv[d].bey.pdf64;
if (a.kdv[d].tah) delete a.kdv[d].tah.pdf64;
}
for (const d in (a.muh || {})) delete a.muh[d].pdf64;
for (const y in (a.yillar || {})) {
const yy = a.yillar[y];
if (yy.kurumlar) delete yy.kurumlar.pdf64;
if (yy.muavin) { yy.muavin = { fileName: yy.muavin.fileName, yuklenme: yy.muavin.yuklenme,
firm: yy.muavin.firm, donem: yy.muavin.donem, hesapSayisi: (yy.muavin.sections || []).length }; }
if (yy.plan) { yy.plan = { fileName: yy.plan.fileName, yuklenme: yy.plan.yuklenme, n: yy.plan.n }; }
}
}
const kveri = {};
for (const y in s.defterDokum) kveri["defterDokum/" + y] = s.defterDokum[y];
kveri["edefterFirma"] = s.edefter.firma || {};
const genel = {};
for (const y in (s.edefter.takvim || {})) genel["edefterTakvim/" + y] = s.edefter.takvim[y];
return { firmalar: s.firmalar, tutanaklar: s.tutanaklar, arsiv: s.arsiv, kveri, genel };
}
/* ---------------- senkron ---------------- */
let esitleZamanlayici = null, esitleBekliyor = false;
function esitleDurum(m, tur) {
const el = $("senkDurum"); if (!el) return;
el.className = "senk " + (tur || "");
el.textContent = m;
}
async function esitle() {
if (!KULLANICI) return;
esitleDurum("kaydediliyor…", "bekle");
try {
const c = sunucuKopyasi();
if (YEREL_MOD) {
await LDB.yaz("ayar", "state", { firmalar: state.firmalar, tutanaklar: state.tutanaklar,
arsiv: c.arsiv, defterDokum: state.defterDokum, edefter: state.edefter });
esitlemeZaman = new Date();
esitleDurum("yerel kayıt " + esitlemeZaman.toLocaleTimeString("tr-TR"), "ok");
esitleBekliyor = false; return;
}
await api("kaydet", c);
esitlemeZaman = new Date();
esitleDurum("kaydedildi " + esitlemeZaman.toLocaleTimeString("tr-TR"), "ok");
SUNUCU_HATA = 0;
} catch (e) {
SUNUCU_HATA++;
esitleDurum("kaydedilemedi: " + e.message, "hata");
if (SUNUCU_HATA === 1) toast("Sunucuya kaydedilemedi: " + e.message);
} finally { esitleBekliyor = false; }
}
/* eski persist() çağrıları buraya düşer: geciktirmeli senkron */
async function persist() {
if (!KULLANICI) return;
esitleBekliyor = true;
esitleDurum("değişiklik var…", "bekle");
clearTimeout(esitleZamanlayici);
esitleZamanlayici = setTimeout(esitle, 1200);
}
window.addEventListener("beforeunload", e => {
if (esitleBekliyor) { e.preventDefault(); e.returnValue = ""; }
});
/* ---------------- açılış ---------------- */
const nesne = v => (v && typeof v === "object" && !Array.isArray(v)) ? v : {};
async function sunucudanYukle() {
if (YEREL_MOD) {
const y = await LDB.oku("ayar", "state");
if (y) { state = Object.assign(state, y); }
migrate(); await agirHidrasyon(); return;
}
const j = await api("veri");
state.firmalar = (j.firmalar || []).map(f => ({ ...f }));
state.tutanaklar = (j.tutanaklar || []).map(t => ({ ...t }));
state.arsiv = nesne(j.arsiv);
for (const fid in state.arsiv) {
const a = state.arsiv[fid] = nesne(state.arsiv[fid]);
a.kdv = nesne(a.kdv); a.muh = nesne(a.muh); a.yillar = nesne(a.yillar);
for (const y in a.yillar) a.yillar[y] = nesne(a.yillar[y]);
}
state.defterDokum = {};
state.edefter = { takvim: {}, firma: {} };
for (const k in (j.kveri || {})) {
if (k.startsWith("defterDokum/")) state.defterDokum[k.split("/")[1]] = j.kveri[k];
else if (k === "edefterFirma") state.edefter.firma = nesne(j.kveri[k]);
}
for (const k in (j.genel || {})) {
if (k.startsWith("edefterTakvim/")) state.edefter.takvim[k.split("/")[1]] = nesne(j.genel[k]);
}
migrate();
await agirHidrasyon();
}
/* sunucudan gelen hafif kayıtlara yerel ağır verileri iliştir */
async function agirHidrasyon() {
for (const fid in state.arsiv) {
const a = state.arsiv[fid];
for (const d in (a.kdv || {})) {
const s = a.kdv[d];
if (s.bey) s.bey.pdf64 = await agirOku(`pdf|${fid}|kdvbey|${d}`) || undefined;
if (s.tah) s.tah.pdf64 = await agirOku(`pdf|${fid}|kdvtah|${d}`) || undefined;
}
for (const d in (a.muh || {})) a.muh[d].pdf64 = await agirOku(`pdf|${fid}|muh|${d}`) || undefined;
for (const y in (a.yillar || {})) {
const yy = a.yillar[y];
if (yy.kurumlar) yy.kurumlar.pdf64 = await agirOku(`pdf|${fid}|kurumlar|${y}`) || undefined;
if (yy.muavin) { const sec = await agirOku(`mu|${fid}|${y}`); if (sec) yy.muavin.sections = sec; }
if (yy.plan) { const dt = await agirOku(`plan|${fid}|${y}`); if (dt) yy.plan.data = dt; }
}
}
}
const esc = s => String(s ?? "").replace(/[&<>"]/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c]));
const trU = s => (s || "").replace(/i/g, "İ").replace(/ı/g, "I").replace(/ç/g, "Ç").replace(/ğ/g, "Ğ").replace(/ö/g, "Ö").replace(/ş/g, "Ş").replace(/ü/g, "Ü").toUpperCase();
const money = n => (n ?? 0).toLocaleString("tr-TR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
function toast(m) { const t = $("toast"); t.textContent = m; t.classList.add("show"); setTimeout(() => t.classList.remove("show"), 2600); }
function pick(id) { $(id).click(); }
/* tarih yardımcıları: gg.aa.yyyy */
function pDate(s) { const m = /^(\d{1,2})[./](\d{1,2})[./](\d{4})$/.exec((s || "").trim()); return m ? new Date(+m[3], +m[2] - 1, +m[1]) : null; }
function fDate(d) { return d ? String(d.getDate()).padStart(2, "0") + "." + String(d.getMonth() + 1).padStart(2, "0") + "." + d.getFullYear() : ""; }
function addMonth(d) { const x = new Date(d); const day = x.getDate(); x.setMonth(x.getMonth() + 1); if (x.getDate() < day) x.setDate(0); return x; }
function daysLeft(s) { const d = pDate(s); if (!d) return null; const t = new Date(); t.setHours(0,0,0,0); return Math.round((d - t) / 864e5); }
function curT() { return state.tutanaklar.find(t => t.id === cur); }
function firmOf(t) { return state.firmalar.find(f => f.id === t.firmaId) || { ad: "?", vkn: "" }; }
/* ---------------- kimlik üretimi ---------------- */
function yeniId(on) {
const r = (crypto && crypto.getRandomValues)
? Array.from(crypto.getRandomValues(new Uint8Array(6))).map(x => x.toString(16).padStart(2, "0")).join("")
: Math.random().toString(16).slice(2, 14);
return on + "-" + r;
}
/* ---------------- oturum arayüzü ---------------- */
function kullaniciGoster() {
const el = $("kullaniciKutu"); if (!el || !KULLANICI) return;
if (YEREL_MOD) {
el.innerHTML = `
`;
}).join("");
}
function addFirm(preAd, preVkn, preIl, preVd) {
const ad = prompt("Firma ünvanı:", preAd || ""); if (!ad) return;
const vkn = prompt("Firma VKN (10 hane):", preVkn || "") || "";
const il = prompt("Bağlı olduğu il:", preIl || "İSTANBUL") || "";
const vd = prompt("Bağlı olduğu vergi dairesi:", preVd || "") || "";
state.firmalar.push({ id: yeniId("f"), ad: ad.trim(), vkn: vkn.trim(), il: il.trim(), vd: vd.trim() });
persist(); renderFirms(); toast("Firma eklendi");
}
function copyFirm(id) { const f = state.firmalar.find(x => x.id === id); addFirm(f.ad + " (KOPYA)", f.vkn, f.il, f.vd); }
function editFirm(id) {
const f = state.firmalar.find(x => x.id === id);
const ad = prompt("Firma ünvanı:", f.ad); if (!ad) return;
f.ad = ad.trim(); f.vkn = (prompt("Firma VKN:", f.vkn) || f.vkn).trim();
f.il = (prompt("Bağlı olduğu il:", f.il || "İSTANBUL") || f.il || "").trim();
f.vd = (prompt("Bağlı olduğu vergi dairesi:", f.vd || "") || f.vd || "").trim();
persist(); renderFirms(); renderHome();
}
function delFirm(id) {
if (!confirm("Firma ve tutanakları silinsin mi?")) return;
state.firmalar = state.firmalar.filter(f => f.id !== id);
state.tutanaklar = state.tutanaklar.filter(t => t.firmaId !== id);
persist(); renderFirms();
}
/* ---------------- tutanak yaşam döngüsü ---------------- */
function newTutanak(firmaId) {
let fid = firmaId;
if (!fid) {
if (state.firmalar.length === 1) fid = state.firmalar[0].id;
else {
const list = state.firmalar.map((f, i) => (i + 1) + ") " + f.ad).join("\n");
const n = parseInt(prompt("Hangi firma için?\n" + list, "1"));
if (!n || !state.firmalar[n - 1]) return; fid = state.firmalar[n - 1].id;
}
}
const t = { id: yeniId("t"), firmaId: fid, sayi: "", tarih: "", teblig: "", sonTarih: "",
durum: "Bekliyor", mukellef: "", mukellefVkn: "", mukellefVd: "", ymm: "", donemBas: "", donemBit: "",
pdfFaturalar: [], satis: [], alis: [], odemeGenel: "BANKA" };
state.tutanaklar.push(t); persist(); openTut(t.id);
}
function delTut(id) { if (!confirm("Tutanak silinsin mi?")) return; state.tutanaklar = state.tutanaklar.filter(t => t.id !== id); persist(); renderHome(); }
function openTut(id) { cur = id; MU = null; PLAN = null; go("tut"); renderTut(); }
function mSet(k, v) { const t = curT(); if (!t) return; t[k] = v; if (k === "durum" && v === "Gönderildi" && !t.gonderim) t.gonderim = fDate(new Date()); persist(); renderTutHeaderBits(); if (k === "tarih") renderDefter(); if (k === "donemBas" || k === "donemBit") { renderBeyan(); renderMuhtasar(); renderOrtak(); renderDefter(); loadFromArsiv(); } }
function setTeblig(v) {
const t = curT(); t.teblig = v.trim();
const d = pDate(t.teblig);
if (d) { t.sonTarih = fDate(addMonth(d)); $("f_son").value = t.sonTarih; }
persist(); renderTutHeaderBits();
}
function tebligUyari() {
const t = curT(); const inp = $("f_teblig"), h = $("tebligHint");
if (!t || !inp) return;
const bos = !pDate(t.teblig);
inp.classList.toggle("askfill", bos);
if (h) h.classList.toggle("on", bos);
inp.title = bos ? "Tutanağın size tebliğ edildiği tarihi girin — cevap son tarihi buna göre hesaplanır." : "";
}
function renderTutHeaderBits() {
const t = curT(); if (!t) return;
tebligUyari();
const f = firmOf(t);
$("tutTitle").textContent = f.ad.split(" ").slice(0, 2).join(" ") + " — Karşıt İnceleme" + (t.sayi ? " #" + t.sayi : "");
const s = tutStat(t); const k = daysLeft(t.sonTarih);
$("tutBadge").innerHTML = `${s.txt}${k !== null && t.durum !== "Gönderildi" ? " · " + (k < 0 ? Math.abs(k) + " gün geçti" : k + " gün kaldı") : ""}`;
const okPdf = !!t.mukellefVkn, okLuca = !!(MU && PLAN), okCalc = t.satis.length || t.alis.length;
$("st1").className = "step " + (okPdf ? "ok" : "on");
$("st2").className = "step " + (t.teblig ? "ok" : (okPdf ? "on" : ""));
$("st3").className = "step " + (okLuca ? "ok" : (okPdf ? "on" : ""));
$("st4").className = "step " + (okCalc ? "ok" : (okLuca ? "on" : ""));
$("st5").className = "step " + (t.durum !== "Bekliyor" ? "ok" : (okCalc ? "on" : ""));
$("btnCalc").disabled = !(MU && PLAN);
}
function renderTut() {
const t = curT(); if (!t) return;
["sayi", "tarih", "teblig", "mukellef", "ymm", "durum"].forEach(k => $("f_" + (k === "teblig" ? "teblig" : k)).value = t[k] || (k === "durum" ? "Bekliyor" : ""));
$("f_son").value = t.sonTarih || ""; $("f_mvkn").value = t.mukellefVkn || ""; $("f_mvd").value = t.mukellefVd || "";
$("f_dbas").value = t.donemBas || ""; $("f_dbit").value = t.donemBit || "";
$("pdfInvInfo").textContent = t.pdfFaturalar.length ? `PDF'ten ${t.pdfFaturalar.length} fatura satırı okundu — hesaplama sonrası çapraz kontrol yapılır.` : "";
$("dropPdf").classList.toggle("okay", !!t.mukellefVkn);
if (t.mukellefVkn) $("dropPdf").innerHTML = `✓ ${esc(t.mukellef || "PDF okundu")}VKN ${esc(t.mukellefVkn)} · yeni PDF için tıklayın`;
const f0 = firmOf(t);
$("pdfWarn").innerHTML = (t.cVkn && f0.vkn && t.cVkn !== f0.vkn)
? `
⚠️ Tutanaktaki nezdinde inceleme yapılan mükellef VKN'si (${esc(t.cVkn)}) seçili firmanızın VKN'siyle (${esc(f0.vkn)}) uyuşmuyor — tutanak başka firmanıza ait olabilir!
`
: (t.cVkn && f0.vkn ? `
✓ Tutanak, seçili firmanıza ait (VKN ${esc(t.cVkn)} doğrulandı).
` : "");
$("dropPlan").classList.remove("okay"); $("dropMuavin").classList.remove("okay");
$("dropPlan").innerHTML = `Cari Hesap Plan ListesiLuca → cari hesap planı (.xlsx)`;
$("dropMuavin").innerHTML = `Muavin DökümLütfen ilgili yıla ait muavin döküm yükleyiniz (.xlsx)`;
$("calcInfo").textContent = ""; $("donemWarn").innerHTML = "";
loadFromArsiv(); // arşivdeki muavin/cari planı otomatik getir
const y = yilOf(t);
const kk = y ? AY(t.firmaId, y).kurumlar : null;
if (kk && !(t.ortaklar || []).length) applyOrtaklar(t, kk);
renderSales(); renderBuys(); renderBeyan(); renderMuhtasar(); renderOrtak(); renderDefter(); renderTutHeaderBits();
}
/* ================================================================
DOSYA OKUMA
================================================================ */
let MU = null, PLAN = null; // bellekteki muavin/plan (tutanak başına yeniden yüklenir)
function bindDrop(dropId, inputId, handler) {
const d = $(dropId), i = $(inputId);
i.onchange = () => i.files[0] && handler(i.files[0]);
d.ondragover = e => { e.preventDefault(); d.classList.add("over"); };
d.ondragleave = () => d.classList.remove("over");
d.ondrop = e => { e.preventDefault(); d.classList.remove("over"); e.dataTransfer.files[0] && handler(e.dataTransfer.files[0]); };
}
/* ---- PDF satır çıkarma (ortak yardımcı) ---- */
async function extractPdfLines(buf) {
const pdf = await pdfjsLib.getDocument({ data: buf }).promise;
let lines = [];
for (let p = 1; p <= pdf.numPages; p++) {
const pg = await pdf.getPage(p);
const tc = await pg.getTextContent();
const items = tc.items.map(it => ({ x: it.transform[4], y: it.transform[5], s: it.str })).filter(it => it.s.trim());
const rows = {};
items.forEach(it => { const key = Math.round(it.y / 4) * 4; (rows[key] = rows[key] || []).push(it); });
Object.keys(rows).sort((a, b) => b - a).forEach(k => {
lines.push(rows[k].sort((a, b) => a.x - b.x).map(i => i.s).join(" ").replace(/\s+/g, " ").trim());
});
lines.push("§SAYFA§");
}
return lines;
}
async function handlePdf(file) {
try {
const lines = await extractPdfLines(await file.arrayBuffer());
const t = curT();
parseTutanakLines(lines, t);
persist(); renderTut();
toast("PDF okundu: " + (t.mukellef || "bilgiler alınamadı, elle girin"));
} catch (e) { console.error(e); toast("PDF okunamadı: " + e.message); }
}
function parseTutanakLines(lines, t) {
const all = lines.join("\n");
let m = /Tarih\s*:?\s*(\d{2}\.\d{2}\.\d{4})/.exec(all); if (m) t.tarih = m[1];
m = /Sayı\s*:?\s*(\d+)/.exec(all); if (m) t.sayi = m[1];
if (!t.sayi) { m = /:\s*(\d{1,6})\s*$/m.exec(lines.slice(0, 8).join("\n")); if (m) t.sayi = m[1]; }
// Bölüm bazlı: A) ymm, B) mükellef
const idxA = lines.findIndex(l => /A\)\s*KARŞIT İNCELEMEYİ YAPAN/i.test(l));
const idxB = lines.findIndex(l => /B\)\s*TASDİK HİZMETİ VERİLEN/i.test(l));
const idxC = lines.findIndex(l => /C\)\s*NEZDİNDE/i.test(l));
const grab = (seg, re) => { for (const l of seg) { const m = re.exec(l); if (m) return m[1].trim(); } return ""; };
if (idxA >= 0) t.ymm = grab(lines.slice(idxA, idxB > 0 ? idxB : idxA + 12), /ADI VE SOYADI\s+([A-ZÇĞİÖŞÜ][A-ZÇĞİÖŞÜa-zçğıöşü .]+)/) || t.ymm;
if (idxB >= 0) {
const seg = lines.slice(idxB, idxC > 0 ? idxC : idxB + 14);
t.mukellef = grab(seg, /ADI VE SOYADI\s*\/?\s*ÜNVANI\s+(.+)/) || t.mukellef;
t.mukellefVkn = grab(seg, /K[İI]ML[İI]K NUMARASI\s+(\d{10,11})/) || t.mukellefVkn;
t.mukellefVd = grab(seg, /VERG[İI] DA[İI]RES[İI]\s+([A-ZÇĞİÖŞÜ][^\n]*)/) || t.mukellefVd;
}
if (!t.mukellefVkn) { // yedek: ünvan satırının hemen altındaki 10 haneli numara
for (let i = 0; i < lines.length - 1; i++) {
const m1 = /(ŞİRKETİ|ŞTİ\.?|A\.Ş\.?|LTD\.?)\s*$/.exec(lines[i]); const m2 = /^(\d{10})$/.exec(lines[i + 1]);
if (m1 && m2) { t.mukellef = t.mukellef || lines[i]; t.mukellefVkn = m2[1]; break; }
}
}
// Dönem: "İadeye Esas ..." tablosundaki satır (örn: "01.2025 12.2025 01.2026 25.03.2026 ...")
for (const l of lines) {
const m2 = /^(\d{2}\.20\d{2})\s+(\d{2}\.20\d{2})\s+(\d{2}\.20\d{2})/.exec(l);
if (m2) { t.donemBas = m2[1]; t.donemBit = m2[2]; break; }
}
// C) bölümündeki kendi firmamızın VKN'si (yanlış firma seçimini yakalamak için)
if (idxC >= 0) {
const segC = lines.slice(idxC, idxC + 12);
t.cVkn = grab(segC, /K[İI]ML[İI]K NUMARASI\s+(\d{10,11})/) || "";
t.cVd = (grab(segC, /VERG[İI] DA[İI]RES[İI]\s+([A-ZÇĞİÖŞÜ][^\n]*)/) || "").replace(/\s*(VERG[İI] DA[İI]RES[İI].*|M[ÜU]D\.?)$/i, "").trim();
const adr = segC.find(l => /\/[A-ZÇĞİÖŞÜ]+$/.test(l.trim()));
if (adr) { const p = adr.trim().split("/"); t.cIl = trU(p[p.length - 1].trim()); }
// firma kaydında il/vd boşsa doldur
const f1 = firmOf(t);
if (f1 && f1.id) { if (!f1.vd && t.cVd) f1.vd = t.cVd; if (!f1.il && t.cIl) f1.il = t.cIl; }
}
// 3. bölüm fatura listesi — satır grupları:
// a) "10.01.2025 ATA20250000" / "3.800,00 760,00" / "00018" (numara ikiye bölünmüş)
// b) "10.01.2025 ATA2025000000018 3.800,00 760,00" (tek satır)
const invs = [];
for (let i = 0; i < lines.length; i++) {
let m1 = /^(\d{2}\.\d{2}\.\d{4})\s+(\S+)\s+([\d.]+,\d{2})\s+([\d.]+,\d{2})$/.exec(lines[i]);
if (m1) { invs.push({ no: m1[2], tarih: m1[1], tutar: trNum(m1[3]), kdv: trNum(m1[4]) }); continue; }
m1 = /^(\d{2}\.\d{2}\.\d{4})\s+([A-Z0-9]{2,15})$/.exec(lines[i]);
if (m1 && i + 2 < lines.length) {
const m2 = /^([\d.]+,\d{2})\s+([\d.]+,\d{2})$/.exec(lines[i + 1]);
const m3 = /^(\d{1,8})$/.exec(lines[i + 2]);
if (m2 && m3) { invs.push({ no: m1[2] + m3[1], tarih: m1[1], tutar: trNum(m2[1]), kdv: trNum(m2[2]) }); i += 2; }
}
}
t.pdfFaturalar = invs;
}
const trNum = s => parseFloat(String(s).replace(/\./g, "").replace(",", "."));
/* ---- Cari plan ---- */
async function handlePlan(file, yilOverride, firmaIdOverride) {
try {
const wb = XLSX.read(await file.arrayBuffer(), { type: "array" });
const rows = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1, raw: true, defval: "" });
const hdr = rows[0].map(x => trU(String(x)));
const col = n => hdr.findIndex(h => h.includes(n));
const ck = col("HESAP KODU"), cn = col("HESAP ADI"), cv = col("VERGİ NO"), cd = col("VERGİ DAİRESİ"), ct = col("T.C");
if (ck < 0) throw new Error("'HESAP KODU' sütunu bulunamadı — doğru dosya mı?");
const data = {};
rows.slice(1).forEach(r => {
const code = String(r[ck] || "").trim(); if (!code) return;
data[code] = { ad: String(r[cn] || "").trim(), vkn: String(r[cv] || "").trim(), vd: String(r[cd] || "").trim(), tckn: String(r[ct] || "").trim() };
});
// arşive yaz (firma + yıl)
const t = curT();
const fid = firmaIdOverride || (t && t.firmaId);
const yil = yilOverride || (t && yilOf(t));
if (fid && yil) { AY(fid, yil).plan = { fileName: file.name, yuklenme: fDate(new Date()), n: Object.keys(data).length, data };
agirYaz(`plan|${fid}|${yil}`, data); }
if (!yilOverride) { PLAN = data; planShow(); renderTutHeaderBits(); }
persist(); renderArsiv();
toast(`Cari hesap planı okundu (${Object.keys(data).length} hesap)` + (yil ? ` · ${yil} arşivine kaydedildi` : ""));
} catch (e) { toast("Cari plan okunamadı: " + e.message); }
}
function planShow() {
const d = $("dropPlan"); if (!d || !PLAN) return;
const t = curT(); const a = t ? (AY(t.firmaId, yilOf(t)).plan || {}) : {};
d.classList.add("okay");
d.innerHTML = `✓ Cari plan hazır${Object.keys(PLAN).length} hesap${a.yuklenme ? " · arşiv: " + esc(a.yuklenme) : ""} · değiştirmek için tıklayın`;
}
/* ---- Muavin ---- */
/* Excel seri numarasını saat diliminden bağımsız gg.aa.yyyy'ye çevir */
function serialDMY(n) {
const d = new Date(Math.round((n - 25569) * 86400000));
return String(d.getUTCDate()).padStart(2, "0") + "." + String(d.getUTCMonth() + 1).padStart(2, "0") + "." + d.getUTCFullYear();
}
function cellDMY(v, acik) {
// 1) öncelik: açıklamadaki "gg/aa/yyyy-" öneki (muavinde her kayıtta var, saat dilimi sorunu yok)
const m = /^\s*(\d{2})\/(\d{2})\/(\d{4})/.exec(acik || "");
if (m) return m[1] + "." + m[2] + "." + m[3];
// 2) Excel seri numarası → UTC aritmetiği
if (typeof v === "number") return serialDMY(v);
// 3) Date nesnesi → UTC alanları (öğleye kaydırarak gün kaymasını önle)
if (v instanceof Date && !isNaN(v)) { const d = new Date(v.getTime() + 43200000); return String(d.getUTCDate()).padStart(2, "0") + "." + String(d.getUTCMonth() + 1).padStart(2, "0") + "." + d.getUTCFullYear(); }
// 4) metin
if (typeof v === "string") { const m2 = /^(\d{1,2})[./](\d{1,2})[./](\d{4})/.exec(v.trim()); if (m2) return m2[1].padStart(2, "0") + "." + m2[2].padStart(2, "0") + "." + m2[3]; }
return "";
}
async function handleMuavin(file, yilOverride, firmaIdOverride) {
try {
const wb = XLSX.read(await file.arrayBuffer(), { type: "array" });
const rows = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { header: 1, raw: true, defval: null });
const heads = [];
rows.forEach((r, i) => { if (typeof r[0] === "string" && /^\d{3}\./.test(r[0]) && r[4] === "TL") heads.push([i, r[0].trim()]); });
heads.push([rows.length, "END"]);
const sections = [];
for (let h = 0; h < heads.length - 1; h++) {
const [i, name] = heads[h]; const j = heads[h + 1][0];
const code = name.split(/[\s\t]/)[0];
const ents = [];
for (let r = i + 2; r < j; r++) {
const row = rows[r]; if (!row || (row[0] == null && row[3] == null)) continue;
const acik = row[3] != null ? String(row[3]) : "";
ents.push({
tarih: cellDMY(row[0], acik),
fis: row[2] != null ? String(row[2]) : "",
acik,
borc: typeof row[4] === "number" ? row[4] : 0,
alacak: typeof row[5] === "number" ? row[5] : 0
});
}
sections.push({ code, name: name.slice(code.length).trim(), entries: ents });
}
const mu = { fileName: file.name, yuklenme: fDate(new Date()),
firm: rows[1] ? String(rows[1][0] || "") : "", donem: rows[2] ? String(rows[2][1] || "") : "", sections };
const t = curT();
const fid = firmaIdOverride || (t && t.firmaId);
// yıl: dosyadaki dönemden çıkar, yoksa tutanak yılı
const my = (mu.donem.match(/(20\d{2})/) || [])[1];
const yil = yilOverride || my || (t && yilOf(t));
if (fid && yil) { AY(fid, yil).muavin = mu; agirYaz(`mu|${fid}|${yil}`, sections); }
if (!yilOverride) { MU = mu; muavinShow(); muavinDonemWarn(); renderTutHeaderBits(); }
persist(); renderArsiv();
toast(`Muavin okundu (${sections.length} hesap)` + (yil ? ` · ${yil} arşivine kaydedildi` : ""));
} catch (e) { console.error(e); toast("Muavin okunamadı: " + e.message); }
}
function muavinHesapSayisi(m) { return m ? ((m.sections && m.sections.length) || m.hesapSayisi || 0) : 0; }
function muavinShow() {
const d = $("dropMuavin"); if (!d || !MU) return;
d.classList.add("okay");
d.innerHTML = `✓ Muavin hazır${esc((MU.firm || "").slice(0, 34))} · ${esc(MU.donem)} · ${muavinHesapSayisi(MU)} hesap${MU.yuklenme ? " · arşiv: " + esc(MU.yuklenme) : ""} · değiştirmek için tıklayın`;
}
function muavinDonemWarn() {
const t = curT(); if (!t || !MU) return;
if (t.donemBas && MU.donem && !MU.donem.includes(t.donemBas.split(".")[1]))
$("donemWarn").innerHTML = `
⚠️ Dikkat: Tutanak dönemi ${esc(t.donemBas)}–${esc(t.donemBit)}, yüklenen muavin dönemi ise ${esc(MU.donem)}. Lütfen ilgili yıla ait muavin döküm yükleyiniz.
`;
else $("donemWarn").innerHTML = "";
}
/* tutanak açılınca arşivden otomatik yükle */
async function loadFromArsiv() {
const t = curT(); if (!t) return;
const y = yilOf(t); if (!y) return;
const a = AY(t.firmaId, y);
if (a.muavin && !a.muavin.sections) {
const sec = await agirOku(`mu|${t.firmaId}|${y}`);
if (sec) a.muavin.sections = sec;
}
if (a.plan && !a.plan.data) {
const dt = await agirOku(`plan|${t.firmaId}|${y}`);
if (dt) a.plan.data = dt;
}
if (a.muavin && a.muavin.sections) { MU = a.muavin; muavinShow(); muavinDonemWarn(); }
else if (a.muavin) {
$("dropMuavin").innerHTML = `⚠ Muavin bu bilgisayarda yok${esc(a.muavin.fileName || "")} ·
kayıt sunucuda var ama dosya bu cihazda değil — yeniden yükleyin`;
MU = null;
}
if (a.plan && a.plan.data) { PLAN = a.plan.data; planShow(); }
else if (a.plan) {
$("dropPlan").innerHTML = `⚠ Cari plan bu bilgisayarda yokyeniden yükleyin`;
PLAN = null;
}
renderTutHeaderBits();
}
/* ================================================================
HESAPLAMA MOTORU
================================================================ */
const JUNK = /AÇILIŞ|VİRMAN|VIRMAN|DEVİR|KAPAMA|KUR FARKI|ENFLASYON|YANSITMA|Nakli Yekün/i;
function parseAcik(acik) {
const m = /^\s*(\d{2})\/(\d{2})\/(\d{4})-([^-]+)-(.*)$/.exec(acik || "");
if (!m) return { ref: "", firma: (acik || "").trim() };
let rest = m[5].trim();
if (rest.includes("--")) rest = rest.split("--").pop().trim();
return { ref: m[4].trim(), firma: rest.replace(/[,. ]+$/, "") };
}
function splitRef(ref) {
let m = /^([A-ZÇĞİÖŞÜa-z]+)(\d+)$/.exec(ref);
if (m) return [m[1], m[2]];
if (/^\d{16}$/.test(ref)) return [ref.slice(0, 3), ref.slice(3)];
return ["", ref];
}
function normName(s) {
s = trU(s).replace(/[^A-ZÇĞİÖŞÜ0-9 ]/g, " ");
[["SANAYİ","SAN"],["TİCARET","TİC"],["LİMİTED","LTD"],["ŞİRKETİ","ŞTİ"],["ANONİM","A"]].forEach(([a,b]) => s = s.split(a).join(b));
return s.replace(/\s+/g, " ").trim();
}
function planLookup(code, name) {
let p = code && PLAN[code];
if (p && (p.vkn || p.tckn)) return p;
const nn = normName(name);
for (const c in PLAN) { const q = PLAN[c]; if ((q.vkn || q.tckn) && normName(q.ad) === nn) return q; }
return p || { ad: name, vkn: "", vd: "", tckn: "" };
}
function findInFis(fis, ref, codePred) {
const hits = [];
for (const sec of MU.sections) {
if (/^19[013]/.test(sec.code)) continue;
if (!codePred(sec.code)) continue;
for (const e of sec.entries) if (e.fis === fis && ref && e.acik.includes(ref)) hits.push([sec, e]);
}
return hits;
}
function rateOf(name) { const m = /%\s*(\d+)/.exec(name) || /(\d+)\s*%/.exec(name); return m ? +m[1] / 100 : 0.20; }
const fmtD = d => d instanceof Date && !isNaN(d) ? fDate(d) : "";
/* --- alışlar: 191 tabanlı en yüksek N fatura + çapraz kontrol --- */
function computeBuys(nCand = 18) {
const cands = [];
for (const sec of MU.sections) {
if (!sec.code.startsWith("191")) continue;
if (/İADE|IADE|SATIŞ|SATIS/.test(trU(sec.name))) continue;
const rate = rateOf(sec.name);
for (const e of sec.entries) {
if (e.borc <= 0 || JUNK.test(e.acik)) continue;
const { ref, firma } = parseAcik(e.acik);
if (!ref) continue;
cands.push({ kdv: e.borc, rate, tarih: e.tarih, fis: e.fis, ref, firma, kdvHesap: sec.code });
}
}
cands.sort((a, b) => b.kdv - a.kdv);
return cands.slice(0, nCand).map(c => {
const matrah = Math.round(c.kdv / c.rate * 100) / 100;
// cari karşı kaydı (KDV dahil tutar buradan)
const hC = findInFis(c.fis, c.ref, cd => ["320", "329", "336", "321"].includes(cd.slice(0, 3)));
let gross = null, cariSec = null;
hC.forEach(([s, e]) => { if (e.alacak > 0) { gross = e.alacak; cariSec = s; } });
if (gross === null) gross = Math.round((matrah + c.kdv) * 100) / 100;
// matrah kontrolü: fişteki TÜM borç kayıtları (191 ve cari hariç) toplanır — 15'li grup öncelikli
const hD = findInFis(c.fis, c.ref, cd => !/^19/.test(cd) && !/^39/.test(cd) && !["320","329","336","321","120","100","101","102","103","108"].includes(cd.slice(0, 3)))
.filter(([s, e]) => e.borc > 0);
const sum = hD.reduce((a, [s, e]) => a + e.borc, 0);
const sum15 = hD.filter(([s]) => s.code.startsWith("15")).reduce((a, [s, e]) => a + e.borc, 0);
const sum2x = hD.filter(([s]) => /^2/.test(s.code)).reduce((a, [s, e]) => a + e.borc, 0);
const codes = [...new Set(hD.map(([s]) => s.code))];
const duran = sum2x > matrah * 0.5;
let note, level, sel;
if (!hD.length) { note = "15'li grupta ve diğer hesaplarda karşı kayıt bulunamadı – manuel kontrol edin"; level = "bad"; sel = false; }
else {
const ok = Math.abs(sum - matrah) < 1;
const pre = sum15 > 0 ? "" : "15'li grupta bulunamadı; ";
note = pre + codes.join(" + ") + " ile check edildi – " + (ok ? "tutarlı" : "TUTARSIZ (bulunan toplam " + money(sum) + ", matrah " + money(matrah) + ")");
if (duran) note += " · DURAN VARLIK (taşıt/demirbaş — satılan mal kapsamı dışında)";
level = !ok ? "bad" : (duran ? "warn" : (sum15 > 0 ? "ok" : "warn"));
sel = ok && !duran;
}
const p = planLookup(cariSec ? cariSec.code : "", c.firma);
if (!(p.vkn || p.tckn) || !p.vd) { note += " · VKN/vergi dairesi cari planda eksik — elle tamamlayın"; if (level === "ok") level = "warn"; }
const [seri, numara] = splitRef(c.ref);
return { sec: sel, firma: c.firma, vkn: p.vkn || p.tckn, vd: p.vd,
tarih: c.tarih, seri, numara, kdvDahil: gross, matrah, kdv: c.kdv, kontrol: note, level, duran };
});
}
/* --- satışlar: karşı firmanın 120 hesabı --- */
const PAYP = [[/FAST|HAVALE|EFT|IBAN|TR\d{10,}|BANKA|POS|DEKONT/i, "BANKA"], [/ÇEK|CEK/i, "ÇEK"], [/SENET/i, "SENET"], [/NAKİT|NAKIT|KASA|TAHSİLAT/i, "NAKİT"]];
function computeSales(vkn, name) {
const codes = Object.keys(PLAN).filter(c => c.startsWith("120") && vkn && (PLAN[c].vkn === vkn || PLAN[c].tckn === vkn));
let secs = MU.sections.filter(s => s.code.startsWith("120") && (codes.includes(s.code) || normName(s.name) === normName(name)));
if (!secs.length) {
const nn = normName(name).slice(0, 20);
secs = MU.sections.filter(s => s.code.startsWith("120") && nn && normName(s.name).includes(nn));
}
const out = [], pays = [];
for (const sec of secs) {
for (const e of sec.entries) {
if (e.alacak > 0 && !JUNK.test(e.acik)) {
const hit = PAYP.find(([re]) => re.test(e.acik));
pays.push(hit ? hit[1] : "BANKA");
}
if (e.borc <= 0 || JUNK.test(e.acik)) continue;
const { ref } = parseAcik(e.acik);
if (!ref || !/\d{6,}/.test(ref)) continue;
const gross = e.borc;
let kdv = null, kdvSec = null, net600 = null, sec600 = null;
findInFis(e.fis, ref, cd => cd.startsWith("391")).forEach(([s2, e2]) => { if (e2.alacak > 0) { kdv = (kdv || 0) + e2.alacak; kdvSec = s2; } });
findInFis(e.fis, ref, cd => cd.startsWith("60")).forEach(([s2, e2]) => { if (e2.alacak > 0) { net600 = (net600 || 0) + e2.alacak; sec600 = s2; } });
let note, level = "ok";
if (kdv === null && net600 !== null) { kdv = Math.round((gross - net600) * 100) / 100; note = `600 (${sec600.code}) üzerinden hesaplandı`; level = "warn"; }
else if (kdv === null) { kdv = Math.round((gross - gross / 1.2) * 100) / 100; note = "391/600 kaydı bulunamadı; KDV %20 varsayıldı – kontrol edin"; level = "bad"; }
else {
const net = Math.round((gross - kdv) * 100) / 100;
if (net600 !== null && Math.abs(net600 - net) < 1) note = `${kdvSec.code} ve ${sec600.code} ile check edildi – tutarlı`;
else if (net600 !== null) { note = `${kdvSec.code} ile check edildi; 600 toplamı farklı (${money(net600)}) – kontrol edin`; level = "warn"; }
else note = `${kdvSec.code} ile check edildi – tutarlı`;
}
out.push({ tarih: e.tarih, seri: "", numara: ref, tutar: Math.round((gross - kdv) * 100) / 100, kdv: Math.round(kdv * 100) / 100,
kayit: e.tarih, yevmiye: e.fis, odeme: "", kontrol: note, level });
}
}
let dominant = "BANKA";
if (pays.length) { const cnt = {}; pays.forEach(p => cnt[p] = (cnt[p] || 0) + 1); dominant = Object.keys(cnt).sort((a, b) => cnt[b] - cnt[a])[0]; }
out.forEach(r => r.odeme = dominant);
out.sort((a, b) => (pDate(a.tarih) || 0) - (pDate(b.tarih) || 0));
return { rows: out, dominant, hesap: secs.length ? secs[0].code : "" };
}
function calc() {
const t = curT();
if (!MU || !PLAN) { toast("Önce cari plan ve muavin yükleyin"); return; }
if (!t.mukellefVkn && !t.mukellef) { toast("Karşı firma bilgisi yok — PDF yükleyin veya 2. adımda elle girin"); return; }
const s = computeSales(t.mukellefVkn, t.mukellef);
t.satis = s.rows; t.odemeGenel = s.dominant;
t.alis = computeBuys(18);
// seçimleri ilk 10'a ayarla
let n = 0; t.alis.forEach(a => { if (a.sec && n < 10) n++; else a.sec = false; });
$("calcInfo").textContent = s.hesap ? `Karşı firma hesabı: ${s.hesap} · ${t.satis.length} satış faturası bulundu` : "⚠️ Karşı firmanın 120 hesabı bulunamadı!";
persist(); renderSales(); renderBuys(); renderTutHeaderBits();
toast("Tablolar dolduruldu");
}
/* ================================================================
TABLO GÖRÜNÜMLERİ
================================================================ */
function renderSales() {
const t = curT(); if (!t) return;
const tb = $("salesTable").querySelector("tbody");
if (!t.satis.length) { tb.innerHTML = `
Henüz hesaplanmadı — 3. adımda dosyaları yükleyip "İşle"ye basın.
Luca → Yönetici → Diğer İşlemler → Noter Kağıdı menüsünden ilgili yılın Excel'ini indirin. Dosya tüm firmaları kapsar; yılda bir kez yüklemeniz yeterlidir.
Aylık KDV: ${k.aylar.map(m => `${m.d.slice(0, 2)}`).join("")}
${k.onceki.d}
Aylık muhtasar: ${mh.aylar.map(m => `${m.d.slice(0, 2)}`).join("")}
`;
}).join("");
}
async function arsivFiles(files) {
if (!arsivUp) return;
const { fid, yil, tip } = arsivUp;
if (tip === "kdv") await handleBeyanFiles([...files], fid);
else if (tip === "muavin") await handleMuavin(files[0], yil, fid);
else if (tip === "plan") await handlePlan(files[0], yil, fid);
else if (tip === "kurumlar") await handleKurumlar(files[0], yil, fid);
else if (tip === "muh") await handleMuhtasarFiles([...files], fid);
arsivUp = null; renderArsiv();
}
async function beyanFolder() {
if (!window.showDirectoryPicker) { toast("Klasör okuma bu tarayıcıda desteklenmiyor — Chrome/Edge kullanın veya PDF'leri 1. alandan yükleyin"); return; }
try {
const dir = await window.showDirectoryPicker();
const files = [];
async function walk(d, depth) {
for await (const e of d.values()) {
if (e.kind === "file" && /\.pdf$/i.test(e.name)) files.push(await e.getFile());
else if (e.kind === "directory" && depth < 3) await walk(e, depth + 1);
}
}
await walk(dir, 0);
if (!files.length) { toast("Klasörde PDF bulunamadı"); return; }
toast(files.length + " PDF tarandı, işleniyor…");
await handleBeyanFiles(files);
} catch (e) { /* kullanıcı vazgeçti */ }
}
/* tutanak dönemine göre satır listesi: ilgili aylar + en altta önceki dönem */
function beyanRows(t) {
const A = arsiv(t.firmaId);
const pm = s => { const m = /^(\d{2})\.(\d{4})$/.exec(s || ""); return m ? { m: +m[1], y: +m[2] } : null; };
const key = d => String(d.m).padStart(2, "0") + "." + d.y;
let bas = pm(t.donemBas), bit = pm(t.donemBit);
let months = [], onceki = null;
if (bas && bit) {
let d = { ...bas };
while (d.y < bit.y || (d.y === bit.y && d.m <= bit.m)) { months.push(key(d)); d.m++; if (d.m > 12) { d.m = 1; d.y++; } }
const p = { ...bas }; p.m--; if (p.m < 1) { p.m = 12; p.y--; }
onceki = key(p);
months.push(onceki); // önceki dönem en altta
} else {
months = Object.keys(A).sort((a, b) => a.split(".").reverse().join("") < b.split(".").reverse().join("") ? -1 : 1);
// dönem girilmemişse: arşivdeki her yıl için bir önceki yılın Aralık'ı da beklenir
const yillar = [...new Set(months.map(x => x.split(".")[1]))];
for (const y of yillar) {
const dec = "12." + (parseInt(y) - 1);
if (!months.includes(dec) && months.some(x => x.endsWith("." + y) && x !== dec)) { months.push(dec); onceki = onceki || dec; }
}
}
return months.map(dn => {
const s = A[dn] || {};
return { donem: dn, bey: s.bey || null, tah: s.tah || null, onceki: dn === onceki };
});
}
/* eksik kontrolü: önceki dönem ayrı ve önemli — hem beyanname hem tahakkuk aranır */
function beyanEksik(t) {
const rows = beyanRows(t);
const o = rows.find(r => r.onceki);
return {
rows,
oncekiDonem: o ? o.donem : null,
oncekiBeyYok: o ? !o.bey : false,
oncekiTahYok: o ? !o.tah : false,
beyYok: rows.filter(r => !r.onceki && !r.bey).map(r => r.donem),
tahYok: rows.filter(r => !r.onceki && r.bey && !r.tah).map(r => r.donem)
};
}
function renderBeyan() {
const t = curT(); if (!t) return;
const A = arsiv(t.firmaId);
const nDon = Object.keys(A).length;
$("beyanArsivInfo").innerHTML = nDon
? `📁 Firma arşivinde ${nDon} dönem kayıtlı (${Object.keys(A).sort().join(", ")}). Beyanname PDF'leri yedek JSON'a dahildir.`
: "Arşiv boş — beyanname ve tahakkuk PDF'lerini yükleyin.";
const ek = beyanEksik(t);
const rows = ek.rows;
const tb = $("beyanTable").querySelector("tbody");
if (!rows.length) { tb.innerHTML = `
Tutanak dönemi (2. adım) girildiğinde aylar burada listelenir.
`; $("beyanWarn").innerHTML = ""; return; }
// uyarılar — önceki dönem özel olarak vurgulanır
let w = "";
if (ek.oncekiDonem && (ek.oncekiBeyYok || ek.oncekiTahYok)) {
const neler = [ek.oncekiBeyYok ? "beyannamesi" : "", ek.oncekiTahYok ? "tahakkuku" : ""].filter(Boolean).join(" ve ");
w += `
⚠️ ÖNCEKİ DÖNEM EKSİK: ${ek.oncekiDonem} dönemine ait KDV ${neler} yüklenmemiş. Tutanağın 5. bölümü önceki dönemi de ister — lütfen ${ek.oncekiDonem} beyanname ve tahakkuk PDF'lerini de yükleyin.
`;
} else if (ek.oncekiDonem) {
w += `
✓ Önceki dönem (${ek.oncekiDonem}) beyanname ve tahakkuku arşivde mevcut.
`;
}
if (ek.beyYok.length) w += `
İlgili dönemde beyannamesi eksik aylar: ${ek.beyYok.join(", ")}
`;
if (ek.tahYok.length) w += `
Beyannamesi var ama tahakkuku eksik aylar (fiş no yazılamaz): ${ek.tahYok.join(", ")}
`;
$("beyanWarn").innerHTML = w;
tb.innerHTML = rows.map(r => {
const b = r.bey, k = r.tah;
const durum = b && k ? `tam` : b ? `tahakkuk yok` : k ? `beyanname yok` : `eksik`;
const v = x => x === null || x === undefined ? "—" : money(x);
return `
${r.donem}${r.onceki ? ' önceki dönem' : ''}
${b ? v(b.teslim) : "—"}
${b ? v(b.ozel) : "—"}
${b ? v(b.matrah) : "—"}
${b ? v(b.hesaplanan) : "—"}
${b ? v(b.ilave) : "—"}
${b ? v(b.toplam) : "—"}
${b ? v(b.indirimler) : "—"}
${b ? v(b.odenmesi) : "—"}
${b ? v(b.devreden) : "—"}
${k ? esc(k.fisNo || "—") : "—"}
${durum}
`;
}).join("");
}
async function exportBeyan() {
const t = curT(); const f = firmOf(t);
const ek = beyanEksik(t);
const rows = ek.rows.filter(r => r.bey || r.tah);
if (!rows.length) { toast("Arşivde bu döneme ait beyanname yok"); return; }
if (ek.oncekiDonem && (ek.oncekiBeyYok || ek.oncekiTahYok)) {
if (!confirm(`DİKKAT: Önceki dönem (${ek.oncekiDonem}) ${ek.oncekiBeyYok ? "beyannamesi" : "tahakkuku"} eksik!\nTutanağın 5. bölümü önceki dönemi de ister.\n\nYine de eksik haliyle indirilsin mi?`)) return;
}
const eksik = ek.rows.filter(r => !r.bey).map(r => r.donem);
const wb = await makeWb(
["Dönem", "Teslim ve Hizmet Karşılığını Teşkil Eden Bedel", "Özel Matrah Şekline Tabi İşl. Matr. Dahil Olm. Bedel", "KDV Matrahı", "Hesaplanan KDV", "İlave Edilecek KDV", "Toplam KDV", "İndirimler Toplamı", "Ödenmesi Gereken KDV", "Sonraki Döneme Devreden KDV", "Tahakkuk Fişinin Numarası"],
[16, 25, 25, 19, 19, 19, 19, 22, 19, 19, 25],
rows.map(r => [r.donem,
r.bey ? r.bey.teslim : "", r.bey ? r.bey.ozel : "", r.bey ? r.bey.matrah : "", r.bey ? r.bey.hesaplanan : "",
r.bey ? r.bey.ilave : "", r.bey ? r.bey.toplam : "", r.bey ? r.bey.indirimler : "", r.bey ? r.bey.odenmesi : "",
r.bey ? r.bey.devreden : "", r.tah ? (r.tah.fisNo || "") : ""]),
[2, 3, 4, 5, 6, 7, 8, 9, 10], null);
await dl(wb, `${f.vkn}_ONCEKI_DONEM_VE_ILGILI_DONEM_KDV_BEYAN_BILGILERI_${fnDate(t)}.xlsx`);
toast("KDV beyan bilgileri Excel'i indirildi" + (eksik.length ? " · EKSİK aylar: " + eksik.join(", ") : ""));
}
/* ---- yasal defterler: Luca dökümü + e-defter dönemleri ---- */
function defterFirmaEsle(yil, f0) {
const dd = (state.defterDokum || {})[yil];
if (!dd || !Array.isArray(dd.rows)) return null;
const nn = normName(f0.ad);
if (f0.lucaAd) { const h0 = dd.rows.find(r => r.firma === f0.lucaAd); if (h0) return h0; }
let hit = dd.rows.find(r => normName(r.firma) === nn);
if (!hit) hit = dd.rows.find(r => { const n2 = normName(r.firma); return n2.length >= 4 && nn.startsWith(n2); });
if (!hit) hit = dd.rows.find(r => { const n2 = normName(r.firma); return n2.length >= 4 && nn.includes(n2); });
return hit || null;
}
function defterRows(t) {
const f0 = firmOf(t), yil = yilOf(t);
const out = [];
if (!yil) return out;
const hit = defterFirmaEsle(yil, f0);
if (hit) (hit.defterler || []).forEach(d => out.push({
nev: d.ad, bas: "01." + yil, bit: "12." + yil, makam: hit.noter, tarih: d.tarih, no: d.no,
aciklama: "", turAciklama: "", kaynak: "Luca defter dökümü"
}));
const cfg = edefFirma(t.firmaId, yil);
if (cfg.mukellef) {
const ref = pDate(t.tarih) || pDate(t.teblig) || new Date();
const donemler = edefDonemler(t.firmaId, yil);
let secili;
if (t.defterTumEdef) secili = donemler;
else {
secili = donemler.filter(dn => { const sd = pDate(dn.son); return sd && sd >= ref; });
if (!secili.length) secili = donemler.slice(-1); // tüm süreler geçtiyse son dönem yine de yazılır
}
secili.forEach(dn => {
const sd = pDate(dn.son);
const gelmedi = sd && sd >= ref;
["E-DEFTER YEVMİYE DEFTERİ", "E-DEFTER DEFTERİ KEBİR"].forEach(ad => {
const key = ad + "|" + dn.key;
out.push({
nev: ad, bas: dn.bas, bit: dn.bit, makam: ".", tarih: "",
no: ((t.beratNo || {})[key]) || "",
aciklama: gelmedi ? "Süresi gelmediğinden verilmemiştir." : ((t.beratNo || {})[key] ? "" : ""),
turAciklama: "", key,
kaynak: `e-defter · berat son tarihi ${dn.son}` + (gelmedi ? " · süresi gelmedi" : " · süresi doldu, berat no girin")
});
});
});
}
return out;
}
/* Luca dökümünde kâğıt yevmiye/kebir tasdiki yoksa firma e-defter mükellefi kabul edilir */
function edefOtoTespit(t) {
const yil = yilOf(t); if (!yil) return;
const cfg = edefFirma(t.firmaId, yil);
if (cfg.elle) return; // kullanıcı elle ayarladıysa dokunma
const hit = defterFirmaEsle(yil, firmOf(t));
if (!hit) return;
const kagit = (hit.defterler || []).some(d => /YEVMİYE|KEBİR/.test(d.ad) && d.no);
cfg.mukellef = !kagit; cfg.oto = true;
}
function renderDefter() {
const t = curT(); if (!t) return;
edefOtoTespit(t);
const yil = yilOf(t), f0 = firmOf(t);
const dd = (state.defterDokum || {})[yil];
const d = $("dropDefter");
d.classList.toggle("okay", !!dd);
d.innerHTML = dd
? `✓ ${esc(yil)} defter dökümü sistemde${dd.rows.length} firma · ${esc(dd.yuklenme)} · değiştirmek için tıklayın`
: `Luca Defter Döküm Listesi (.xls)${yil ? yil + " yılı dökümü yüklenmemiş" : "önce tutanak dönemini girin"} — tüm firmaları kapsar`;
const hit = yil ? defterFirmaEsle(yil, f0) : null;
const cfg = yil ? edefFirma(t.firmaId, yil) : { mukellef: false };
$("defterInfo").innerHTML = !dd
? `
Noter tasdikli defter bilgileri için ${yil || "ilgili yıl"} defter dökümünü yükleyin.