Auto commit: 2026-02-06T00:30:42.683Z

This commit is contained in:
Flatlogic Bot 2026-02-06 00:30:42 +00:00
parent cddb69516f
commit f52acbf5c3
12 changed files with 361 additions and 13 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -149,6 +149,12 @@
<span>Gösterge Paneli</span> <span>Gösterge Paneli</span>
</a> </a>
</li> </li>
<li>
<a href="{% url 'fatura_otomatik_yukle' %}">
<i class="bi bi-magic"></i>
<span>Otomatik Yükle</span>
</a>
</li>
<li class="{% if active_menu == 'archive' %}active{% endif %}"> <li class="{% if active_menu == 'archive' %}active{% endif %}">
<a href="{% url 'fatura_arsivi' %}"> <a href="{% url 'fatura_arsivi' %}">
<i class="bi bi-collection-fill"></i> <i class="bi bi-collection-fill"></i>
@ -201,4 +207,4 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -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 %}
<div class="row justify-content-center">
<div class="col-md-8">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-4 border-0 shadow-sm mb-4" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="card border-0 shadow-sm">
<div class="card-body p-5 text-center">
<div class="mb-4">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 100px; height: 100px;">
<i class="bi bi-file-earmark-pdf text-primary" style="font-size: 3rem;"></i>
</div>
<h3 class="fw-bold mt-2">Yapay Zeka ile Otomatik Analiz</h3>
<p class="text-muted">Fatura PDF dosyasını seçin, sistemimiz bilgileri otomatik olarak çıkarsın.</p>
</div>
<form method="post" enctype="multipart/form-data" id="auto-upload-form">
{% csrf_token %}
<div class="upload-zone p-5 border border-2 border-dashed rounded-4 mb-4" id="drop-zone">
<input type="file" name="pdf_dosyasi" id="pdf_file" class="d-none" accept=".pdf">
<div id="upload-prompt">
<i class="bi bi-cloud-arrow-up fs-1 text-primary mb-3 d-block"></i>
<label for="pdf_file" class="btn btn-primary rounded-pill px-4 py-2 mb-2 cursor-pointer">
Dosya Seçin
</label>
<p class="mb-0 text-secondary">Veya faturayı buraya sürükleyip bırakın</p>
<small class="text-muted d-block mt-2">Sadece PDF dosyaları desteklenmektedir.</small>
</div>
</div>
<div id="loading-spinner" class="d-none mb-4 py-4">
<div class="spinner-grow text-primary" style="width: 3rem; height: 3rem;" role="status">
<span class="visually-hidden">Analiz ediliyor...</span>
</div>
<h5 class="mt-4 fw-bold text-primary">Fatura Analiz Ediliyor</h5>
<p class="text-muted">Yapay zeka verileri ayrıştırıyor, bu işlem yaklaşık 10-15 saniye sürebilir...</p>
<div class="progress mt-4 mx-auto" style="height: 6px; max-width: 300px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 100%"></div>
</div>
</div>
<div class="d-flex justify-content-center gap-3" id="action-buttons">
<a href="{% url 'home' %}" class="btn btn-light rounded-pill px-4 border">Vazgeç</a>
</div>
</form>
</div>
</div>
<div class="row mt-4">
<div class="col-md-6">
<div class="card border-0 shadow-sm h-100 rounded-4">
<div class="card-body p-4">
<h6 class="fw-bold text-primary mb-3"><i class="bi bi-search me-2"></i>Akıllı Eşleştirme</h6>
<p class="small text-muted mb-0">Sistemimiz Mersis, VKN ve TCKN bilgilerini otomatik tanıyarak firmaları eşleştirir. Firma kayıtlı değilse otomatik oluşturulur.</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border-0 shadow-sm h-100 rounded-4">
<div class="card-body p-4">
<h6 class="fw-bold text-primary mb-3"><i class="bi bi-list-check me-2"></i>Kalem Detayları</h6>
<p class="small text-muted mb-0">Fatura içerisindeki tüm ürün ve hizmet kalemleri, adetleri ve KDV oranları ile birlikte otomatik olarak listelenir.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
.upload-zone {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background-color: #f8fafc;
cursor: pointer;
border-color: #e2e8f0 !important;
}
.upload-zone:hover, .upload-zone.dragover {
border-color: var(--primary-color) !important;
background-color: #f0f7ff;
transform: scale(1.01);
}
.cursor-pointer {
cursor: pointer;
}
</style>
<script>
const fileInput = document.getElementById('pdf_file');
const dropZone = document.getElementById('drop-zone');
const form = document.getElementById('auto-upload-form');
const loadingSpinner = document.getElementById('loading-spinner');
const uploadPrompt = document.getElementById('upload-prompt');
const actionButtons = document.getElementById('action-buttons');
function startUpload() {
if (fileInput.files.length > 0) {
loadingSpinner.classList.remove('d-none');
uploadPrompt.classList.add('d-none');
dropZone.classList.add('border-0', 'bg-white');
dropZone.style.pointerEvents = 'none';
actionButtons.classList.add('d-none');
form.submit();
}
}
fileInput.onchange = startUpload;
dropZone.onclick = (e) => {
if (e.target.tagName !== 'INPUT') {
fileInput.click();
}
};
dropZone.ondragover = (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
};
dropZone.ondragleave = () => {
dropZone.classList.remove('dragover');
};
dropZone.ondrop = (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
const files = e.dataTransfer.files;
if (files[0].type === 'application/pdf') {
fileInput.files = files;
startUpload();
} else {
alert('Lütfen sadece PDF dosyası yükleyin.');
}
}
};
</script>
{% endblock %}

View File

@ -16,7 +16,7 @@
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold"><i class="bi bi-files me-2 text-primary"></i>Tüm Faturalar</h5> <h5 class="mb-0 fw-bold"><i class="bi bi-files me-2 text-primary"></i>Tüm Faturalar</h5>
<div class="btn-group"> <div class="btn-group">
<a href="{% url 'fatura_ekle' %}?firma={{ firma.pk }}" class="btn btn-sm btn-primary rounded-pill px-3"><i class="bi bi-plus-lg me-1"></i> Yeni PDF Yükle</a> <a href="{% url 'fatura_otomatik_yukle' %}" class="btn btn-sm btn-primary rounded-pill px-3"><i class="bi bi-magic me-1"></i> Yeni PDF Yükle (Otomatik)</a>
</div> </div>
</div> </div>
</div> </div>
@ -73,4 +73,4 @@
</table> </table>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -113,13 +113,13 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="d-grid gap-3"> <div class="d-grid gap-3">
<a href="{% url 'fatura_ekle' %}" class="btn btn-primary text-start p-3 rounded-4 d-flex align-items-center shadow-sm border-0 text-decoration-none" style="background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);"> <a href="{% url 'fatura_otomatik_yukle' %}" class="btn btn-primary text-start p-3 rounded-4 d-flex align-items-center shadow-sm border-0 text-decoration-none" style="background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);">
<div class="bg-white bg-opacity-25 rounded-3 p-2 me-3"> <div class="bg-white bg-opacity-25 rounded-3 p-2 me-3">
<i class="bi bi-plus-circle-fill fs-4"></i> <i class="bi bi-magic fs-4"></i>
</div> </div>
<div> <div>
<div class="fw-bold text-white">Fatura Yükle</div> <div class="fw-bold text-white">Fatura Yükle</div>
<div class="small text-white opacity-75">PDF dosyasını seçin</div> <div class="small text-white opacity-75">Yapay zeka ile otomatik analiz</div>
</div> </div>
</a> </a>
@ -132,14 +132,18 @@
<div class="small text-muted">Sisteme yeni tedarikçi kaydet</div> <div class="small text-muted">Sisteme yeni tedarikçi kaydet</div>
</div> </div>
</a> </a>
<a href="{% url 'fatura_ekle' %}" class="btn btn-link text-muted text-center small mt-2">
Manuel fatura yükle (Eski yöntem)
</a>
</div> </div>
<div class="mt-4 p-4 rounded-4 bg-primary bg-opacity-10 border border-primary border-opacity-10"> <div class="mt-4 p-4 rounded-4 bg-primary bg-opacity-10 border border-primary border-opacity-10">
<h6 class="fw-bold text-primary mb-2"><i class="bi bi-info-circle-fill me-2"></i>Biliyor muydunuz?</h6> <h6 class="fw-bold text-primary mb-2"><i class="bi bi-info-circle-fill me-2"></i>Akıllı Analiz</h6>
<p class="small text-muted mb-0">Sistemimiz OCR teknolojisi sayesinde faturadaki tüm kalemleri otomatik olarak ayrıştırabilir.</p> <p class="small text-muted mb-0">Sistemimiz Mersis, VKN ve TCKN bilgilerini otomatik tanıyarak firmaları eşleştirir.</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from .views import ( from .views import (
home, fatura_arsivi, firma_detay, fatura_detay, raporlar, 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 = [ urlpatterns = [
@ -12,7 +12,8 @@ urlpatterns = [
path("raporlar/", raporlar, name="raporlar"), path("raporlar/", raporlar, name="raporlar"),
path("firma/ekle/", firma_ekle, name="firma_ekle"), path("firma/ekle/", firma_ekle, name="firma_ekle"),
path("fatura/ekle/", fatura_ekle, name="fatura_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("arama/", search, name="search"),
path("firma/<int:pk>/sil/", firma_sil, name="firma_sil"), path("firma/<int:pk>/sil/", firma_sil, name="firma_sil"),
path("fatura/<int:pk>/sil/", fatura_sil, name="fatura_sil"), path("fatura/<int:pk>/sil/", fatura_sil, name="fatura_sil"),
] ]

75
core/utils.py Normal file
View File

@ -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

View File

@ -1,7 +1,12 @@
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.db.models import Sum, Count, Avg, Q 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 .models import Firma, Fatura, FaturaKalemi
from .forms import FirmaForm, FaturaForm from .forms import FirmaForm, FaturaForm
from .utils import extract_text_from_pdf, analyze_invoice_text
import os
def home(request): def home(request):
"""Fatura Yönetimi Gösterge Paneli.""" """Fatura Yönetimi Gösterge Paneli."""
@ -97,11 +102,120 @@ def fatura_ekle(request):
form = FaturaForm(request.POST, request.FILES) form = FaturaForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
fatura = form.save() fatura = form.save()
return redirect('firma_detay', pk=fatura.firma.pk) return redirect('fatura_detay', pk=fatura.pk)
else: else:
form = FaturaForm(initial=initial) form = FaturaForm(initial=initial)
return render(request, 'core/fatura_form.html', {'form': form, 'title': 'Yeni Fatura Yükle'}) 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): def search(request):
query = request.GET.get('q', '') query = request.GET.get('q', '')
faturalar = [] faturalar = []
@ -136,4 +250,4 @@ def fatura_sil(request, pk):
if request.method == 'POST': if request.method == 'POST':
fatura.delete() fatura.delete()
return redirect('firma_detay', pk=firma_pk) return redirect('firma_detay', pk=firma_pk)
return render(request, 'core/confirm_delete.html', {'object': fatura, 'type': 'Fatura'}) return render(request, 'core/confirm_delete.html', {'object': fatura, 'type': 'Fatura'})