diff --git a/ai/__pycache__/__init__.cpython-311.pyc b/ai/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000..9beeae7 Binary files /dev/null and b/ai/__pycache__/__init__.cpython-311.pyc differ diff --git a/ai/__pycache__/local_ai_api.cpython-311.pyc b/ai/__pycache__/local_ai_api.cpython-311.pyc new file mode 100644 index 0000000..ae12bda Binary files /dev/null and b/ai/__pycache__/local_ai_api.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 6159c03..5c722f4 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..8ee4570 Binary files /dev/null and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index cac8e17..91dbf86 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/base.html b/core/templates/base.html index f530a5f..1e0316d 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -149,6 +149,12 @@ Gösterge Paneli +
  • + + + Otomatik Yükle + +
  • @@ -201,4 +207,4 @@ {% block extra_js %}{% endblock %} - \ No newline at end of file + diff --git a/core/templates/core/fatura_form_otomatik.html b/core/templates/core/fatura_form_otomatik.html new file mode 100644 index 0000000..005cd08 --- /dev/null +++ b/core/templates/core/fatura_form_otomatik.html @@ -0,0 +1,148 @@ +{% extends "base.html" %} + +{% block title %}Otomatik Fatura Yükle - FaturaYol{% endblock %} +{% block page_title %}Otomatik Fatura Yükle{% endblock %} + +{% block content %} +
    + +
    + + + + +{% endblock %} diff --git a/core/templates/core/firma_detay.html b/core/templates/core/firma_detay.html index daee070..1288faa 100644 --- a/core/templates/core/firma_detay.html +++ b/core/templates/core/firma_detay.html @@ -16,7 +16,7 @@
    Tüm Faturalar
    @@ -73,4 +73,4 @@ -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index d8cbf0d..472250b 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -113,13 +113,13 @@
    - +
    - +
    Fatura Yükle
    -
    PDF dosyasını seçin
    +
    Yapay zeka ile otomatik analiz
    @@ -132,14 +132,18 @@
    Sisteme yeni tedarikçi kaydet
    + + + Manuel fatura yükle (Eski yöntem) +
    -
    Biliyor muydunuz?
    -

    Sistemimiz OCR teknolojisi sayesinde faturadaki tüm kalemleri otomatik olarak ayrıştırabilir.

    +
    Akıllı Analiz
    +

    Sistemimiz Mersis, VKN ve TCKN bilgilerini otomatik tanıyarak firmaları eşleştirir.

    -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 8421686..a9b68fb 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,7 @@ from django.urls import path from .views import ( home, fatura_arsivi, firma_detay, fatura_detay, raporlar, - firma_ekle, fatura_ekle, search, firma_sil, fatura_sil + firma_ekle, fatura_ekle, fatura_otomatik_yukle, search, firma_sil, fatura_sil ) urlpatterns = [ @@ -12,7 +12,8 @@ urlpatterns = [ path("raporlar/", raporlar, name="raporlar"), path("firma/ekle/", firma_ekle, name="firma_ekle"), path("fatura/ekle/", fatura_ekle, name="fatura_ekle"), + path("fatura/otomatik-yukle/", fatura_otomatik_yukle, name="fatura_otomatik_yukle"), path("arama/", search, name="search"), path("firma//sil/", firma_sil, name="firma_sil"), path("fatura//sil/", fatura_sil, name="fatura_sil"), -] \ No newline at end of file +] diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..007ceb9 --- /dev/null +++ b/core/utils.py @@ -0,0 +1,75 @@ +import os +from pypdf import PdfReader +from ai.local_ai_api import LocalAIApi +import json + +def extract_text_from_pdf(pdf_path): + try: + reader = PdfReader(pdf_path) + text = "" + for page in reader.pages: + text += page.extract_text() + "\n" + + if not text.strip(): + # If no text extracted, maybe it's an image-based PDF + # In a real environment we would use OCR here + return None + return text + except Exception as e: + print(f"Error extracting text from PDF: {e}") + return None + +def analyze_invoice_text(text): + prompt = f""" + Sen profesyonel bir fatura veri ayıklama sistemisin. Aşağıdaki fatura metnini analiz et ve bilgileri kesinlikle JSON formatında döndür. + + Özellikle şu bilgileri bulmaya çalış: + - Satıcı Firma Bilgileri: Adı, Vergi Kimlik Numarası (VKN), MERSİS Numarası, Adresi. + - Fatura Bilgileri: Fatura No, Tarih, Ara Toplam, KDV Tutarı, Genel Toplam. + - Satır Kalemleri: Her bir ürün veya hizmetin adı, adedi, birim fiyatı, KDV oranı, KDV tutarı ve toplam tutarı. + + Dikkat: + - Vergi numarası 10 haneli (VKN) veya 11 haneli (TCKN) olabilir. + - MERSİS no genellikle 16 hanelidir. + - Sayısal değerleri (tutarlar, adetler) sadece rakam ve nokta (ondalık için) olarak döndür. + - Tarihi YYYY-MM-DD formatına çevir. + - Eğer bir veri bulunamazsa null döndür. + + İstenen JSON formatı: + {{ + "firma_adi": "...", + "vergi_no": "...", + "mersis_no": "...", + "adres": "...", + "fatura_no": "...", + "tarih": "YYYY-MM-DD", + "ara_toplam": 0.00, + "kdv_toplam": 0.00, + "genel_toplam": 0.00, + "kalemler": [ + {{ + "urun_adi": "...", + "adet": 1, + "birim_fiyat": 0.00, + "kdv_orani": 20, + "kdv_tutari": 0.00, + "toplam_tutar": 0.00 + }} + ] + }} + + Fatura Metni: + {text} + """ + + response = LocalAIApi.create_response({ + "input": [ + {"role": "system", "content": "Sen sadece JSON döndüren, hata payı düşük bir fatura analiz uzmanısın. Yanıtında asla JSON dışında metin bulundurma."}, + {"role": "user", "content": prompt}, + ], + "text": {"format": {"type": "json_object"}}, + }) + + if response.get("success"): + return LocalAIApi.decode_json_from_response(response) + return None diff --git a/core/views.py b/core/views.py index 54fe897..c94a1d7 100644 --- a/core/views.py +++ b/core/views.py @@ -1,7 +1,12 @@ from django.shortcuts import render, get_object_or_404, redirect from django.db.models import Sum, Count, Avg, Q +from django.db import transaction +from django.core.files.storage import default_storage +from django.contrib import messages from .models import Firma, Fatura, FaturaKalemi from .forms import FirmaForm, FaturaForm +from .utils import extract_text_from_pdf, analyze_invoice_text +import os def home(request): """Fatura Yönetimi Gösterge Paneli.""" @@ -97,11 +102,120 @@ def fatura_ekle(request): form = FaturaForm(request.POST, request.FILES) if form.is_valid(): fatura = form.save() - return redirect('firma_detay', pk=fatura.firma.pk) + return redirect('fatura_detay', pk=fatura.pk) else: form = FaturaForm(initial=initial) return render(request, 'core/fatura_form.html', {'form': form, 'title': 'Yeni Fatura Yükle'}) +def fatura_otomatik_yukle(request): + if request.method == 'POST' and request.FILES.get('pdf_dosyasi'): + pdf_file = request.FILES['pdf_dosyasi'] + + # Save temporary file to extract text + temp_name = 'temp_' + pdf_file.name + path = default_storage.save('temp/' + temp_name, pdf_file) + full_path = default_storage.path(path) + + try: + text = extract_text_from_pdf(full_path) + if not text: + messages.error(request, "PDF dosyasından metin okunamadı. Dosya taranmış bir resim olabilir veya şifreli olabilir.") + else: + data = analyze_invoice_text(text) + if not data: + messages.error(request, "Yapay zeka faturayı analiz edemedi. Lütfen dosyanın geçerli bir fatura olduğundan emin olun.") + else: + with transaction.atomic(): + # Smarter Firma matching + vergi_no = str(data.get('vergi_no', '')).strip() + mersis_no = str(data.get('mersis_no', '')).strip() + firma_adi = data.get('firma_adi', '').strip() or 'Bilinmeyen Firma' + + firma = None + + # 1. Try by Vergi No / TCKN + if vergi_no and vergi_no != 'None' and vergi_no != 'null': + firma = Firma.objects.filter(vergi_no=vergi_no).first() + + # 2. Try by Mersis No + if not firma and mersis_no and mersis_no != 'None' and mersis_no != 'null': + firma = Firma.objects.filter(mersis_no=mersis_no).first() + + # 3. Try by Name (Exact) + if not firma and firma_adi != 'Bilinmeyen Firma': + firma = Firma.objects.filter(ad__iexact=firma_adi).first() + + # 4. Create if not found + if not firma: + # Ensure we have a unique vergi_no even if AI failed + if not vergi_no or vergi_no == 'None' or vergi_no == 'null': + vergi_no = f"AUTO-{os.urandom(4).hex()}" + + firma = Firma.objects.create( + ad=firma_adi, + vergi_no=vergi_no, + mersis_no=mersis_no if (mersis_no != 'None' and mersis_no != 'null') else None, + adres=data.get('adres', '') + ) + else: + # Update missing info if found existing firma + updated = False + if not firma.mersis_no and mersis_no and mersis_no != 'None' and mersis_no != 'null': + firma.mersis_no = mersis_no + updated = True + if not firma.adres and data.get('adres'): + firma.adres = data.get('adres') + updated = True + if updated: + firma.save() + + # Create Fatura + fatura_no = data.get('fatura_no') or f"AUTO-{os.urandom(4).hex()}" + + # Check if this invoice already exists for this firm + fatura = Fatura.objects.filter(fatura_no=fatura_no, firma=firma).first() + if not fatura: + fatura = Fatura.objects.create( + firma=firma, + fatura_no=fatura_no, + tarih=data.get('tarih') or '2026-01-01', + ara_toplam=data.get('ara_toplam') or 0, + kdv_toplam=data.get('kdv_toplam') or 0, + genel_toplam=data.get('genel_toplam') or 0, + pdf_dosyasi=pdf_file, + islenmis=True + ) + + # Add Items + kalemler = data.get('kalemler', []) + if isinstance(kalemler, list): + for k in kalemler: + FaturaKalemi.objects.create( + fatura=fatura, + urun_adi=k.get('urun_adi') or 'Ürün', + adet=k.get('adet') or 1, + birim_fiyat=k.get('birim_fiyat') or 0, + kdv_orani=k.get('kdv_orani') or 20, + kdv_tutari=k.get('kdv_tutari') or 0, + toplam_tutar=k.get('toplam_tutar') or 0 + ) + + # Clean up temp file + if default_storage.exists(path): + default_storage.delete(path) + + messages.success(request, f"Fatura başarıyla analiz edildi ve {firma.ad} firmasına eklendi.") + return redirect('fatura_detay', pk=fatura.pk) + except Exception as e: + messages.error(request, f"Fatura işlenirken bir hata oluştu: {str(e)}") + print(f"Error processing automatic invoice: {e}") + finally: + # Clean up temp file + if default_storage.exists(path): + default_storage.delete(path) + + return render(request, 'core/fatura_form_otomatik.html', {'title': 'Otomatik Fatura Yükle', 'active_menu': 'dashboard'}) + def search(request): query = request.GET.get('q', '') faturalar = [] @@ -136,4 +250,4 @@ def fatura_sil(request, pk): if request.method == 'POST': fatura.delete() return redirect('firma_detay', pk=firma_pk) - return render(request, 'core/confirm_delete.html', {'object': fatura, 'type': 'Fatura'}) \ No newline at end of file + return render(request, 'core/confirm_delete.html', {'object': fatura, 'type': 'Fatura'})