Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f52acbf5c3 | ||
|
|
cddb69516f | ||
|
|
e6fa05f94e |
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.
BIN
assets/pasted-20260206-000101-e0693223.png
Normal file
BIN
assets/pasted-20260206-000101-e0693223.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 109 KiB |
Binary file not shown.
Binary file not shown.
@ -43,14 +43,9 @@ CSRF_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_SAMESITE = "None"
|
||||
CSRF_COOKIE_SAMESITE = "None"
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
@ -63,10 +58,7 @@ MIDDLEWARE = [
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
# Disable X-Frame-Options middleware to allow Flatlogic preview iframes.
|
||||
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
X_FRAME_OPTIONS = 'ALLOWALL'
|
||||
@ -81,7 +73,6 @@ TEMPLATES = [
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
# IMPORTANT: do not remove – injects PROJECT_DESCRIPTION/PROJECT_IMAGE_URL and cache-busting timestamp
|
||||
'core.context_processors.project_context',
|
||||
@ -110,26 +101,6 @@ DATABASES = {
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||
|
||||
|
||||
@ -14,13 +14,11 @@ Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path("", include("core.urls")),
|
||||
]
|
||||
|
||||
|
||||
Binary file not shown.
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
BIN
core/__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
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.
@ -1,3 +1,24 @@
|
||||
from django.contrib import admin
|
||||
from .models import Firma, Fatura, FaturaKalemi
|
||||
|
||||
# Register your models here.
|
||||
@admin.register(Firma)
|
||||
class FirmaAdmin(admin.ModelAdmin):
|
||||
list_display = ('ad', 'vergi_no', 'olusturulma_tarihi')
|
||||
search_fields = ('ad', 'vergi_no')
|
||||
|
||||
class FaturaKalemiInline(admin.TabularInline):
|
||||
model = FaturaKalemi
|
||||
extra = 1
|
||||
|
||||
@admin.register(Fatura)
|
||||
class FaturaAdmin(admin.ModelAdmin):
|
||||
list_display = ('fatura_no', 'firma', 'tarih', 'genel_toplam', 'islenmis')
|
||||
list_filter = ('islenmis', 'tarih', 'firma')
|
||||
search_fields = ('fatura_no', 'firma__ad')
|
||||
inlines = [FaturaKalemiInline]
|
||||
|
||||
@admin.register(FaturaKalemi)
|
||||
class FaturaKalemiAdmin(admin.ModelAdmin):
|
||||
list_display = ('urun_adi', 'fatura', 'adet', 'toplam_tutar')
|
||||
list_filter = ('fatura__firma',)
|
||||
search_fields = ('urun_adi', 'fatura__fatura_no')
|
||||
27
core/forms.py
Normal file
27
core/forms.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django import forms
|
||||
from .models import Firma, Fatura, FaturaKalemi
|
||||
|
||||
class FirmaForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Firma
|
||||
fields = ['ad', 'vergi_no', 'mersis_no', 'adres']
|
||||
widgets = {
|
||||
'ad': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Firma Tam Adı'}),
|
||||
'vergi_no': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Vergi No / TC Kimlik No'}),
|
||||
'mersis_no': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'MERSİS No (Opsiyonel)'}),
|
||||
'adres': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Firma Adresi'}),
|
||||
}
|
||||
|
||||
class FaturaForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Fatura
|
||||
fields = ['firma', 'fatura_no', 'tarih', 'ara_toplam', 'kdv_toplam', 'genel_toplam', 'pdf_dosyasi']
|
||||
widgets = {
|
||||
'firma': forms.Select(attrs={'class': 'form-select'}),
|
||||
'fatura_no': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Fatura No'}),
|
||||
'tarih': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'ara_toplam': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'kdv_toplam': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'genel_toplam': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'pdf_dosyasi': forms.FileInput(attrs={'class': 'form-control'}),
|
||||
}
|
||||
71
core/migrations/0001_initial.py
Normal file
71
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,71 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-05 22:03
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Fatura',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('fatura_no', models.CharField(max_length=50, unique=True, verbose_name='Fatura No')),
|
||||
('tarih', models.DateField(verbose_name='Fatura Tarihi')),
|
||||
('ara_toplam', models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='Ara Toplam')),
|
||||
('kdv_toplam', models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='KDV Toplam')),
|
||||
('genel_toplam', models.DecimalField(decimal_places=2, default=0, max_digits=12, verbose_name='Genel Toplam')),
|
||||
('pdf_dosyasi', models.FileField(upload_to='faturalar/%Y/%m/', verbose_name='PDF Dosyası')),
|
||||
('islenmis', models.BooleanField(default=False, verbose_name='İşlendi mi?')),
|
||||
('olusturulma_tarihi', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Fatura',
|
||||
'verbose_name_plural': 'Faturalar',
|
||||
'ordering': ['-tarih'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Firma',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ad', models.CharField(max_length=255, verbose_name='Firma Adı')),
|
||||
('vergi_no', models.CharField(max_length=20, unique=True, verbose_name='Vergi No / TC Kimlik No')),
|
||||
('mersis_no', models.CharField(blank=True, max_length=30, null=True, verbose_name='MERSİS No')),
|
||||
('adres', models.TextField(blank=True, null=True, verbose_name='Adres')),
|
||||
('olusturulma_tarihi', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Firma',
|
||||
'verbose_name_plural': 'Firmalar',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FaturaKalemi',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('urun_adi', models.CharField(max_length=255, verbose_name='Ürün Adı')),
|
||||
('adet', models.DecimalField(decimal_places=2, default=1, max_digits=10, verbose_name='Adet')),
|
||||
('birim_fiyat', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Birim Fiyat')),
|
||||
('kdv_orani', models.DecimalField(decimal_places=2, default=20, max_digits=5, verbose_name='KDV Oranı (%)')),
|
||||
('kdv_tutari', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='KDV Tutarı')),
|
||||
('toplam_tutar', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Toplam Tutar')),
|
||||
('fatura', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='kalemler', to='core.fatura', verbose_name='Fatura')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Fatura Kalemi',
|
||||
'verbose_name_plural': 'Fatura Kalemleri',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='fatura',
|
||||
name='firma',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='faturalar', to='core.firma', verbose_name='Firma'),
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
@ -1,3 +1,50 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
class Firma(models.Model):
|
||||
ad = models.CharField(max_length=255, verbose_name="Firma Adı")
|
||||
vergi_no = models.CharField(max_length=20, unique=True, verbose_name="Vergi No / TC Kimlik No")
|
||||
mersis_no = models.CharField(max_length=30, blank=True, null=True, verbose_name="MERSİS No")
|
||||
adres = models.TextField(blank=True, null=True, verbose_name="Adres")
|
||||
olusturulma_tarihi = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.ad
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Firma"
|
||||
verbose_name_plural = "Firmalar"
|
||||
|
||||
class Fatura(models.Model):
|
||||
firma = models.ForeignKey(Firma, on_delete=models.CASCADE, related_name="faturalar", verbose_name="Firma")
|
||||
fatura_no = models.CharField(max_length=50, unique=True, verbose_name="Fatura No")
|
||||
tarih = models.DateField(verbose_name="Fatura Tarihi")
|
||||
ara_toplam = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name="Ara Toplam")
|
||||
kdv_toplam = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name="KDV Toplam")
|
||||
genel_toplam = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name="Genel Toplam")
|
||||
pdf_dosyasi = models.FileField(upload_to="faturalar/%Y/%m/", verbose_name="PDF Dosyası")
|
||||
islenmis = models.BooleanField(default=False, verbose_name="İşlendi mi?")
|
||||
olusturulma_tarihi = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.fatura_no} - {self.firma.ad}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Fatura"
|
||||
verbose_name_plural = "Faturalar"
|
||||
ordering = ['-tarih']
|
||||
|
||||
class FaturaKalemi(models.Model):
|
||||
fatura = models.ForeignKey(Fatura, on_delete=models.CASCADE, related_name="kalemler", verbose_name="Fatura")
|
||||
urun_adi = models.CharField(max_length=255, verbose_name="Ürün Adı")
|
||||
adet = models.DecimalField(max_digits=10, decimal_places=2, default=1, verbose_name="Adet")
|
||||
birim_fiyat = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="Birim Fiyat")
|
||||
kdv_orani = models.DecimalField(max_digits=5, decimal_places=2, default=20, verbose_name="KDV Oranı (%)")
|
||||
kdv_tutari = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="KDV Tutarı")
|
||||
toplam_tutar = models.DecimalField(max_digits=12, decimal_places=2, verbose_name="Toplam Tutar")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.urun_adi} ({self.fatura.fatura_no})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Fatura Kalemi"
|
||||
verbose_name_plural = "Fatura Kalemleri"
|
||||
|
||||
@ -1,25 +1,210 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<html lang="tr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Knowledge Base{% endblock %}</title>
|
||||
{% if project_description %}
|
||||
<meta name="description" content="{{ project_description }}">
|
||||
<meta property="og:description" content="{{ project_description }}">
|
||||
<meta property="twitter:description" content="{{ project_description }}">
|
||||
{% endif %}
|
||||
{% if project_image_url %}
|
||||
<meta property="og:image" content="{{ project_image_url }}">
|
||||
<meta property="twitter:image" content="{{ project_image_url }}">
|
||||
{% endif %}
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
|
||||
{% block head %}{% endblock %}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}FaturaYol - Akıllı Fatura Yönetimi{% endblock %}</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
{% load static %}
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #2563eb;
|
||||
--sidebar-bg: #0f172a;
|
||||
--sidebar-hover: #1e293b;
|
||||
--sidebar-active: #334155;
|
||||
--bg-body: #f1f5f9;
|
||||
--text-main: #1e293b;
|
||||
--text-muted: #64748b;
|
||||
}
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: var(--bg-body);
|
||||
color: var(--text-main);
|
||||
overflow-x: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
}
|
||||
#sidebar {
|
||||
width: 280px;
|
||||
min-width: 280px;
|
||||
background: var(--sidebar-bg);
|
||||
color: #fff;
|
||||
transition: all 0.3s;
|
||||
z-index: 1000;
|
||||
box-shadow: 4px 0 10px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#sidebar .sidebar-header {
|
||||
padding: 30px 25px;
|
||||
background: rgba(0,0,0,0.2);
|
||||
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||
}
|
||||
#sidebar ul.components {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
#sidebar ul li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
#sidebar ul li a {
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
#sidebar ul li a:hover {
|
||||
color: #fff;
|
||||
background: var(--sidebar-hover);
|
||||
}
|
||||
#sidebar ul li.active > a {
|
||||
color: #fff;
|
||||
background: var(--sidebar-active);
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
#sidebar ul li a i {
|
||||
margin-right: 15px;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
#content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 40px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.top-navbar {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
padding: 15px 25px;
|
||||
margin-bottom: 35px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card {
|
||||
border: none;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: var(--primary-color);
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.badge-soft-success { background-color: #d1fae5; color: #065f46; }
|
||||
.badge-soft-primary { background-color: #dbeafe; color: #1e40af; }
|
||||
|
||||
@media (max-width: 992px) {
|
||||
#sidebar {
|
||||
margin-left: -280px;
|
||||
}
|
||||
#sidebar.active {
|
||||
margin-left: 0;
|
||||
}
|
||||
#content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{% block content %}{% endblock %}
|
||||
</body>
|
||||
<div id="wrapper">
|
||||
<!-- Sidebar -->
|
||||
<nav id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<a href="{% url 'home' %}" class="text-white text-decoration-none">
|
||||
<h3 class="mb-0 fw-bold d-flex align-items-center">
|
||||
<i class="bi bi-lightning-charge-fill text-primary me-2"></i>
|
||||
FaturaYol
|
||||
</h3>
|
||||
</a>
|
||||
<small class="text-muted">Akıllı Arşivleme Sistemi</small>
|
||||
</div>
|
||||
|
||||
<ul class="list-unstyled components">
|
||||
<li class="{% if active_menu == 'dashboard' %}active{% endif %}">
|
||||
<a href="{% url 'home' %}">
|
||||
<i class="bi bi-grid-1x2-fill"></i>
|
||||
<span>Gösterge Paneli</span>
|
||||
</a>
|
||||
</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 %}">
|
||||
<a href="{% url 'fatura_arsivi' %}">
|
||||
<i class="bi bi-collection-fill"></i>
|
||||
<span>Fatura Arşivi</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="{% if active_menu == 'reports' %}active{% endif %}">
|
||||
<a href="{% url 'raporlar' %}">
|
||||
<i class="bi bi-bar-chart-line-fill"></i>
|
||||
<span>İstatistik & Raporlar</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-auto p-4">
|
||||
<div class="card bg-primary text-white border-0 p-3" style="background: linear-gradient(45deg, #2563eb, #4f46e5);">
|
||||
<small class="opacity-75 mb-1">Kullanım Durumu</small>
|
||||
<div class="progress mb-2" style="height: 6px; background: rgba(255,255,255,0.2);">
|
||||
<div class="progress-bar bg-white" style="width: 45%;"></div>
|
||||
</div>
|
||||
<small class="small">Sürüm 1.0.4</small>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Page Content -->
|
||||
<div id="content">
|
||||
<div class="container-fluid p-0">
|
||||
<div class="top-navbar">
|
||||
<div class="d-flex align-items-center">
|
||||
<h5 class="mb-0 fw-bold">{% block page_title %}Genel Bakış{% endblock %}</h5>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-4 d-none d-md-block">
|
||||
<form action="{% url 'search' %}" method="get">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-0"><i class="bi bi-search"></i></span>
|
||||
<input type="text" name="q" class="form-control bg-light border-0" placeholder="Fatura veya firma ara..." style="width: 300px;" value="{{ query|default:'' }}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
30
core/templates/core/confirm_delete.html
Normal file
30
core/templates/core/confirm_delete.html
Normal file
@ -0,0 +1,30 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Silmeyi Onayla - FaturaYol{% endblock %}
|
||||
{% block page_title %}{{ type }} Silme İşlemi{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-5 text-center">
|
||||
<div class="bg-danger bg-opacity-10 text-danger mx-auto mb-4 rounded-circle d-flex align-items-center justify-content-center" style="width: 80px; height: 80px;">
|
||||
<i class="bi bi-exclamation-triangle-fill fs-1"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-3">Emin misiniz?</h4>
|
||||
<p class="text-muted mb-4">
|
||||
<strong>{{ object }}</strong> isimli {{ type|lower }} kaydını silmek üzeresiniz.
|
||||
Bu işlem geri alınamaz ve bu kayda bağlı tüm veriler silinecektir.
|
||||
</p>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-danger rounded-pill py-2 fw-bold">Evet, Sil</button>
|
||||
<a href="{{ request.META.HTTP_REFERER|default:'/' }}" class="btn btn-light rounded-pill py-2">İptal</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
57
core/templates/core/fatura_arsivi.html
Normal file
57
core/templates/core/fatura_arsivi.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Fatura Arşivi - FaturaYol{% endblock %}
|
||||
{% block page_title %}Fatura Arşivi{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<p class="text-muted">Firmalara göre gruplanmış fatura listesi. Detaylı görüntülemek için bir firmaya tıklayın.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<a href="{% url 'firma_ekle' %}" class="btn btn-primary rounded-pill px-4">
|
||||
<i class="bi bi-folder-plus me-2"></i> Yeni Firma Ekle
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for firma in firmalar %}
|
||||
<div class="col-md-4 col-xl-3 mb-4">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4 text-center">
|
||||
<div class="position-absolute top-0 end-0 p-2">
|
||||
<a href="{% url 'firma_sil' firma.pk %}" class="btn btn-link text-danger p-0" title="Firmayı Sil"><i class="bi bi-trash"></i></a>
|
||||
</div>
|
||||
<div class="bg-primary-soft stat-icon mx-auto mb-3" style="width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #e0e7ff; color: #4338ca;">
|
||||
<i class="bi bi-building" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-1 text-truncate">{{ firma.ad }}</h5>
|
||||
<p class="text-muted small mb-3">VN: {{ firma.vergi_no }}</p>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center bg-light p-2 rounded-3 mb-3">
|
||||
<div class="text-start ps-2">
|
||||
<small class="text-muted d-block" style="font-size: 0.65rem; text-transform: uppercase;">Fatura</small>
|
||||
<span class="fw-bold">{{ firma.fatura_sayisi }} Adet</span>
|
||||
</div>
|
||||
<div class="text-end pe-2">
|
||||
<small class="text-muted d-block" style="font-size: 0.65rem; text-transform: uppercase;">Toplam</small>
|
||||
<span class="fw-bold text-primary">{{ firma.toplam_tutar|default:0|floatformat:2 }} ₺</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{% url 'firma_detay' firma.pk %}" class="btn btn-outline-primary btn-sm w-100 rounded-pill">
|
||||
Dosyaları Görüntüle <i class="bi bi-chevron-right ms-1"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<i class="bi bi-folder2-open text-muted" style="font-size: 4rem;"></i>
|
||||
<p class="mt-3 text-muted">Henüz kayıtlı bir firma veya fatura bulunamadı.</p>
|
||||
<a href="{% url 'firma_ekle' %}" class="btn btn-primary mt-2">İlk Firmayı Ekle</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
105
core/templates/core/fatura_detay.html
Normal file
105
core/templates/core/fatura_detay.html
Normal file
@ -0,0 +1,105 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Fatura Detayı - {{ fatura.fatura_no }}{% endblock %}
|
||||
{% block page_title %}Fatura Detayı{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'fatura_arsivi' %}" class="text-decoration-none">Arşiv</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'firma_detay' fatura.firma.pk %}" class="text-decoration-none">{{ fatura.firma.ad }}</a></li>
|
||||
<li class="breadcrumb-item active">{{ fatura.fatura_no }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<!-- PDF Önizleme Kartı (Simüle edilmiş, PDF.js veya iframe kullanılabilir) -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-bold">PDF Önizleme</h5>
|
||||
<a href="{{ fatura.pdf_dosyasi.url }}" target="_blank" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-box-arrow-up-right me-1"></i> Tam Ekran
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body p-0" style="height: 600px; background: #525659;">
|
||||
{% if fatura.pdf_dosyasi %}
|
||||
<iframe src="{{ fatura.pdf_dosyasi.url }}" width="100%" height="100%" frameborder="0"></iframe>
|
||||
{% else %}
|
||||
<div class="h-100 d-flex align-items-center justify-content-center text-white flex-column">
|
||||
<i class="bi bi-file-earmark-pdf mb-3" style="font-size: 3rem;"></i>
|
||||
<p>PDF dosyası bulunamadı.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<!-- Fatura Bilgileri Yan Panel -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="fw-bold mb-4">Fatura Bilgileri</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small text-uppercase fw-bold">Firma</label>
|
||||
<p class="fw-bold mb-0 text-primary">{{ fatura.firma.ad }}</p>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-6">
|
||||
<label class="text-muted small text-uppercase fw-bold">Tarih</label>
|
||||
<p class="fw-bold mb-0">{{ fatura.tarih|date:"d.m.Y" }}</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="text-muted small text-uppercase fw-bold">Fatura No</label>
|
||||
<p class="fw-bold mb-0">{{ fatura.fatura_no }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">Ara Toplam</span>
|
||||
<span class="fw-bold">{{ fatura.ara_toplam }} ₺</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span class="text-muted">KDV Toplam</span>
|
||||
<span class="fw-bold">{{ fatura.kdv_toplam }} ₺</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-3 pt-3 border-top">
|
||||
<span class="h5 fw-bold mb-0">Genel Toplam</span>
|
||||
<span class="h5 fw-bold mb-0 text-primary">{{ fatura.genel_toplam }} ₺</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kalem Detayları -->
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="p-3 border-bottom">
|
||||
<h5 class="fw-bold mb-0">Ürün / Hizmet Kalemleri</h5>
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
{% for kalem in kalemler %}
|
||||
<div class="list-group-item p-3">
|
||||
<div class="d-flex justify-content-between align-items-start mb-1">
|
||||
<h6 class="fw-bold mb-0">{{ kalem.urun_adi }}</h6>
|
||||
<span class="badge bg-light text-dark">{{ kalem.adet }} Adet</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between small text-muted">
|
||||
<span>Birim: {{ kalem.birim_fiyat }} ₺</span>
|
||||
<span class="fw-bold text-dark">Toplam: {{ kalem.toplam_tutar }} ₺</span>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="p-4 text-center text-muted">
|
||||
Kalem verisi bulunamadı.
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
37
core/templates/core/fatura_form.html
Normal file
37
core/templates/core/fatura_form.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }} - FaturaYol{% endblock %}
|
||||
{% block page_title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<h5 class="fw-bold mb-3">Fatura Detayları</h5>
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="text-danger small">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<a href="{% url 'home' %}" class="btn btn-light rounded-pill px-4">İptal</a>
|
||||
<button type="submit" class="btn btn-primary rounded-pill px-5">Faturayı Yükle</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
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 %}
|
||||
76
core/templates/core/firma_detay.html
Normal file
76
core/templates/core/firma_detay.html
Normal file
@ -0,0 +1,76 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Fatura Arşivi - FaturaYol{% endblock %}
|
||||
{% block page_title %}{{ firma.ad }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'fatura_arsivi' %}" class="text-decoration-none">Arşiv</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ firma.ad }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<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>
|
||||
<div class="btn-group">
|
||||
<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 class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ps-4">Fatura No</th>
|
||||
<th>Tarih</th>
|
||||
<th>Ara Toplam</th>
|
||||
<th>KDV</th>
|
||||
<th>Genel Toplam</th>
|
||||
<th>Durum</th>
|
||||
<th class="text-end pe-4">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fatura in faturalar %}
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">{{ fatura.fatura_no }}</td>
|
||||
<td>{{ fatura.tarih|date:"d.m.Y" }}</td>
|
||||
<td>{{ fatura.ara_toplam }} ₺</td>
|
||||
<td>{{ fatura.kdv_toplam }} ₺</td>
|
||||
<td class="fw-bold text-primary">{{ fatura.genel_toplam }} ₺</td>
|
||||
<td>
|
||||
{% if fatura.islenmis %}
|
||||
<span class="badge badge-soft-success px-2 py-1 rounded-pill">İşlendi</span>
|
||||
{% else %}
|
||||
<span class="badge bg-light text-dark px-2 py-1 rounded-pill border">Bekliyor</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<a href="{% url 'fatura_detay' fatura.pk %}" class="btn btn-sm btn-light rounded-circle me-1" title="Görüntüle">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
{% if fatura.pdf_dosyasi %}
|
||||
<a href="{{ fatura.pdf_dosyasi.url }}" target="_blank" class="btn btn-sm btn-light rounded-circle me-1" title="PDF İndir">
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'fatura_sil' fatura.pk %}" class="btn btn-sm btn-light text-danger rounded-circle" title="Faturayı Sil">
|
||||
<i class="bi bi-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center py-5 text-muted">
|
||||
<p class="mb-0">Bu firmaya ait henüz fatura bulunamadı.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
37
core/templates/core/firma_form.html
Normal file
37
core/templates/core/firma_form.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }} - FaturaYol{% endblock %}
|
||||
{% block page_title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<h5 class="fw-bold mb-3">Firma Bilgileri</h5>
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text">{{ field.help_text }}</div>
|
||||
{% endif %}
|
||||
{% for error in field.errors %}
|
||||
<div class="text-danger small">{{ error }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-4">
|
||||
<a href="{% url 'fatura_arsivi' %}" class="btn btn-light rounded-pill px-4">İptal</a>
|
||||
<button type="submit" class="btn btn-primary rounded-pill px-5">Kaydet</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,145 +1,149 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ project_name }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@keyframes bg-pan {
|
||||
0% {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 100% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2.5rem 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
|
||||
font-weight: 700;
|
||||
margin: 0 0 1.2rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin: 1.5rem auto;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.runtime code {
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
padding: 0.15rem 0.45rem;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.75;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block title %}Gösterge Paneli | FaturaYol{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your app…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<div class="row">
|
||||
<div class="col-12 mb-4">
|
||||
<h2 class="fw-bold">Gösterge Paneli</h2>
|
||||
<p class="text-muted">Fatura yönetim sistemine genel bakış ve hızlı istatistikler.</p>
|
||||
</div>
|
||||
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
|
||||
<p class="runtime">
|
||||
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
|
||||
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card stat-card border-0 shadow-sm">
|
||||
<div class="card-body d-flex align-items-center p-4">
|
||||
<div class="bg-primary-soft stat-icon me-3" style="width: 50px; height: 50px; border-radius: 12px; display: flex; align-items: center; justify-content: center; background: #dbeafe; color: #2563eb;">
|
||||
<i class="bi bi-building" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1 small text-uppercase fw-bold">Toplam Firma</h6>
|
||||
<h3 class="fw-bold mb-0 text-primary">{{ toplam_firma }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card stat-card border-0 shadow-sm">
|
||||
<div class="card-body d-flex align-items-center p-4">
|
||||
<div class="bg-success-soft stat-icon me-3" style="width: 50px; height: 50px; border-radius: 12px; display: flex; align-items: center; justify-content: center; background: #dcfce7; color: #15803d;">
|
||||
<i class="bi bi-file-earmark-text" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1 small text-uppercase fw-bold">Toplam Fatura</h6>
|
||||
<h3 class="fw-bold mb-0 text-success">{{ toplam_fatura }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card stat-card border-0 shadow-sm">
|
||||
<div class="card-body d-flex align-items-center p-4">
|
||||
<div class="bg-warning-soft stat-icon me-3" style="width: 50px; height: 50px; border-radius: 12px; display: flex; align-items: center; justify-content: center; background: #fef9c3; color: #a16207;">
|
||||
<i class="bi bi-cash-stack" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h6 class="text-muted mb-1 small text-uppercase fw-bold">Toplam Harcama</h6>
|
||||
<h3 class="fw-bold mb-0 text-warning">₺{{ toplam_harcama|floatformat:2 }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<!-- Recent Invoices Table -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white py-3 d-flex align-items-center justify-content-between border-0">
|
||||
<h5 class="mb-0 fw-bold"><i class="bi bi-clock-history me-2 text-primary"></i>Son Eklenen Faturalar</h5>
|
||||
<a href="{% url 'fatura_arsivi' %}" class="btn btn-sm btn-link text-decoration-none">Tümünü Gör <i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr class="bg-light">
|
||||
<th class="ps-4 border-0">Fatura No</th>
|
||||
<th class="border-0">Firma</th>
|
||||
<th class="border-0">Tarih</th>
|
||||
<th class="border-0">Toplam</th>
|
||||
<th class="text-end pe-4 border-0">İşlem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fatura in son_faturalar %}
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium text-dark">{{ fatura.fatura_no }}</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark fw-medium border px-2 py-1">{{ fatura.firma.ad }}</span>
|
||||
</td>
|
||||
<td class="text-muted">{{ fatura.tarih|date:"d.m.Y" }}</td>
|
||||
<td class="fw-bold text-primary">₺{{ fatura.genel_toplam }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<a href="{% url 'fatura_detay' fatura.pk %}" class="btn btn-sm btn-light rounded-pill">
|
||||
<i class="bi bi-eye"></i> İncele
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-5">
|
||||
<div class="opacity-50 mb-3">
|
||||
<i class="bi bi-file-earmark-plus" style="font-size: 3rem;"></i>
|
||||
</div>
|
||||
<p class="text-muted mb-0">Henüz fatura eklenmemiş.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-0 shadow-sm h-100">
|
||||
<div class="card-header bg-white py-3 border-0">
|
||||
<h5 class="mb-0 fw-bold"><i class="bi bi-lightning-fill me-2 text-warning"></i>Hızlı İşlemler</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-grid gap-3">
|
||||
<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">
|
||||
<i class="bi bi-magic fs-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold text-white">Fatura Yükle</div>
|
||||
<div class="small text-white opacity-75">Yapay zeka ile otomatik analiz</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="{% url 'firma_ekle' %}" class="btn btn-outline-light text-dark text-start p-3 rounded-4 d-flex align-items-center border bg-light bg-opacity-50 text-decoration-none">
|
||||
<div class="bg-white rounded-3 p-2 me-3 shadow-sm text-primary">
|
||||
<i class="bi bi-folder-plus fs-4"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold text-dark">Yeni Firma Ekle</div>
|
||||
<div class="small text-muted">Sisteme yeni tedarikçi kaydet</div>
|
||||
</div>
|
||||
</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 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>Akıllı Analiz</h6>
|
||||
<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>
|
||||
{% endblock %}
|
||||
68
core/templates/core/raporlar.html
Normal file
68
core/templates/core/raporlar.html
Normal file
@ -0,0 +1,68 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}İstatistik & Raporlar - FaturaYol{% endblock %}
|
||||
{% block page_title %}İstatistik & Raporlar{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="fw-bold mb-4">Aylık Harcama Trendi</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ay</th>
|
||||
<th>Fatura Adedi</th>
|
||||
<th class="text-end">Toplam Tutar</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for harcama in aylik_harcama %}
|
||||
<tr>
|
||||
<td>{{ harcama.tarih__month }}. Ay</td>
|
||||
<td>{{ harcama.adet }}</td>
|
||||
<td class="text-end fw-bold">{{ harcama.toplam }} ₺</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-4">Veri bulunamadı</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="fw-bold mb-4">KDV Özeti</h5>
|
||||
<div class="p-4 bg-light rounded-4 text-center mb-4">
|
||||
<small class="text-muted d-block mb-1">Toplam Ödenen KDV</small>
|
||||
<h2 class="fw-bold text-success mb-0">{{ kdv_ozet.toplam_kdv|default:0 }} ₺</h2>
|
||||
</div>
|
||||
<div class="p-4 bg-light rounded-4 text-center">
|
||||
<small class="text-muted d-block mb-1">Fatura Başı Ortalama KDV</small>
|
||||
<h2 class="fw-bold text-primary mb-0">{{ kdv_ozet.ortalama_kdv|default:0|floatformat:2 }} ₺</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body py-5 text-center">
|
||||
<i class="bi bi-graph-up-arrow text-primary opacity-25" style="font-size: 4rem;"></i>
|
||||
<h4 class="mt-3 fw-bold">Gelişmiş Raporlama Yakında</h4>
|
||||
<p class="text-muted">Grafikler, Excel dışa aktarma ve kategori bazlı analizler bir sonraki güncellemede eklenecektir.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
61
core/templates/core/search_results.html
Normal file
61
core/templates/core/search_results.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Arama Sonuçları - FaturaYol{% endblock %}
|
||||
{% block page_title %}"{{ query }}" için Arama Sonuçları{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="mb-5">
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-building me-2 text-primary"></i>Firmalar ({{ firmalar|length }})</h5>
|
||||
<div class="row">
|
||||
{% for firma in firmalar %}
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-bold mb-1">{{ firma.ad }}</h6>
|
||||
<p class="small text-muted mb-2">VN: {{ firma.vergi_no }}</p>
|
||||
<a href="{% url 'firma_detay' firma.pk %}" class="btn btn-sm btn-outline-primary rounded-pill">Detaya Git</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-muted">Eşleşen firma bulunamadı.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="fw-bold mb-3"><i class="bi bi-file-earmark-text me-2 text-primary"></i>Faturalar ({{ faturalar|length }})</h5>
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ps-4">Fatura No</th>
|
||||
<th>Firma</th>
|
||||
<th>Tarih</th>
|
||||
<th>Genel Toplam</th>
|
||||
<th class="text-end pe-4">İşlem</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fatura in faturalar %}
|
||||
<tr>
|
||||
<td class="ps-4 fw-medium">{{ fatura.fatura_no }}</td>
|
||||
<td>{{ fatura.firma.ad }}</td>
|
||||
<td>{{ fatura.tarih|date:"d.m.Y" }}</td>
|
||||
<td class="fw-bold">₺{{ fatura.genel_toplam }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<a href="{% url 'fatura_detay' fatura.pk %}" class="btn btn-sm btn-light rounded-pill">İncele</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-4 text-muted">Eşleşen fatura bulunamadı.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
16
core/urls.py
16
core/urls.py
@ -1,7 +1,19 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import home
|
||||
from .views import (
|
||||
home, fatura_arsivi, firma_detay, fatura_detay, raporlar,
|
||||
firma_ekle, fatura_ekle, fatura_otomatik_yukle, search, firma_sil, fatura_sil
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("", home, name="home"),
|
||||
path("arsiv/", fatura_arsivi, name="fatura_arsivi"),
|
||||
path("firma/<int:pk>/", firma_detay, name="firma_detay"),
|
||||
path("fatura/<int:pk>/", fatura_detay, name="fatura_detay"),
|
||||
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/<int:pk>/sil/", firma_sil, name="firma_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
|
||||
264
core/views.py
264
core/views.py
@ -1,25 +1,253 @@
|
||||
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
|
||||
import platform
|
||||
|
||||
from django import get_version as django_version
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def home(request):
|
||||
"""Render the landing screen with loader and environment details."""
|
||||
host_name = request.get_host().lower()
|
||||
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
|
||||
now = timezone.now()
|
||||
"""Fatura Yönetimi Gösterge Paneli."""
|
||||
toplam_firma = Firma.objects.count()
|
||||
toplam_fatura = Fatura.objects.count()
|
||||
toplam_harcama = Fatura.objects.aggregate(Sum('genel_toplam'))['genel_toplam__sum'] or 0
|
||||
son_faturalar = Fatura.objects.select_related('firma').all().order_by('-olusturulma_tarihi')[:10]
|
||||
|
||||
context = {
|
||||
"project_name": "New Style",
|
||||
"agent_brand": agent_brand,
|
||||
"django_version": django_version(),
|
||||
"python_version": platform.python_version(),
|
||||
"current_time": now,
|
||||
"host_name": host_name,
|
||||
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
|
||||
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
|
||||
"toplam_firma": toplam_firma,
|
||||
"toplam_fatura": toplam_fatura,
|
||||
"toplam_harcama": toplam_harcama,
|
||||
"son_faturalar": son_faturalar,
|
||||
"active_menu": "dashboard"
|
||||
}
|
||||
return render(request, "core/index.html", context)
|
||||
|
||||
def fatura_arsivi(request):
|
||||
"""Firmalara göre gruplanmış fatura arşivi."""
|
||||
firmalar = Firma.objects.annotate(
|
||||
fatura_sayisi=Count('faturalar'),
|
||||
toplam_tutar=Sum('faturalar__genel_toplam')
|
||||
).order_by('ad')
|
||||
|
||||
context = {
|
||||
"firmalar": firmalar,
|
||||
"active_menu": "archive"
|
||||
}
|
||||
return render(request, "core/fatura_arsivi.html", context)
|
||||
|
||||
def firma_detay(request, pk):
|
||||
"""Belirli bir firmanın fatura listesi."""
|
||||
firma = get_object_or_404(Firma, pk=pk)
|
||||
faturalar = firma.faturalar.all().order_by('-tarih')
|
||||
|
||||
context = {
|
||||
"firma": firma,
|
||||
"faturalar": faturalar,
|
||||
"active_menu": "archive"
|
||||
}
|
||||
return render(request, "core/firma_detay.html", context)
|
||||
|
||||
def fatura_detay(request, pk):
|
||||
"""Fatura detay önizleme ve kalemleri."""
|
||||
fatura = get_object_or_404(Fatura.objects.select_related('firma'), pk=pk)
|
||||
kalemler = fatura.kalemler.all()
|
||||
|
||||
context = {
|
||||
"fatura": fatura,
|
||||
"kalemler": kalemler,
|
||||
"active_menu": "archive"
|
||||
}
|
||||
return render(request, "core/fatura_detay.html", context)
|
||||
|
||||
def raporlar(request):
|
||||
"""İstatistik ve Raporlar."""
|
||||
# Aylık harcama trendi
|
||||
aylik_harcama = Fatura.objects.values('tarih__month').annotate(
|
||||
toplam=Sum('genel_toplam'),
|
||||
adet=Count('id')
|
||||
).order_by('tarih__month')
|
||||
|
||||
# KDV dağılımı
|
||||
kdv_ozet = Fatura.objects.aggregate(
|
||||
toplam_kdv=Sum('kdv_toplam'),
|
||||
ortalama_kdv=Avg('kdv_toplam')
|
||||
)
|
||||
|
||||
context = {
|
||||
"aylik_harcama": aylik_harcama,
|
||||
"kdv_ozet": kdv_ozet,
|
||||
"active_menu": "reports"
|
||||
}
|
||||
return render(request, "core/raporlar.html", context)
|
||||
|
||||
def firma_ekle(request):
|
||||
if request.method == 'POST':
|
||||
form = FirmaForm(request.POST)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect('fatura_arsivi')
|
||||
else:
|
||||
form = FirmaForm()
|
||||
return render(request, 'core/firma_form.html', {'form': form, 'title': 'Yeni Firma Ekle'})
|
||||
|
||||
def fatura_ekle(request):
|
||||
firma_id = request.GET.get('firma')
|
||||
initial = {}
|
||||
if firma_id:
|
||||
initial['firma'] = firma_id
|
||||
|
||||
if request.method == 'POST':
|
||||
form = FaturaForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
fatura = form.save()
|
||||
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 = []
|
||||
firmalar = []
|
||||
if query:
|
||||
faturalar = Fatura.objects.filter(
|
||||
Q(fatura_no__icontains=query) |
|
||||
Q(firma__ad__icontains=query)
|
||||
).select_related('firma')
|
||||
firmalar = Firma.objects.filter(
|
||||
Q(ad__icontains=query) |
|
||||
Q(vergi_no__icontains=query)
|
||||
)
|
||||
|
||||
context = {
|
||||
'query': query,
|
||||
'faturalar': faturalar,
|
||||
'firmalar': firmalar,
|
||||
}
|
||||
return render(request, 'core/search_results.html', context)
|
||||
|
||||
def firma_sil(request, pk):
|
||||
firma = get_object_or_404(Firma, pk=pk)
|
||||
if request.method == 'POST':
|
||||
firma.delete()
|
||||
return redirect('fatura_arsivi')
|
||||
return render(request, 'core/confirm_delete.html', {'object': firma, 'type': 'Firma'})
|
||||
|
||||
def fatura_sil(request, pk):
|
||||
fatura = get_object_or_404(Fatura, pk=pk)
|
||||
firma_pk = fatura.firma.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'})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user