Auto commit: 2026-02-06T00:30:42.683Z
This commit is contained in:
parent
cddb69516f
commit
f52acbf5c3
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
ai/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal file
BIN
ai/__pycache__/local_ai_api.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/utils.cpython-311.pyc
Normal file
BIN
core/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -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>
|
||||||
|
|||||||
148
core/templates/core/fatura_form_otomatik.html
Normal file
148
core/templates/core/fatura_form_otomatik.html
Normal 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 %}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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,11 +132,15 @@
|
|||||||
<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>
|
||||||
|
|||||||
@ -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,6 +12,7 @@ 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
75
core/utils.py
Normal 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
|
||||||
116
core/views.py
116
core/views.py
@ -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 = []
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user