yeni
This commit is contained in:
888
ssh_manager/invoice_views.py
Normal file
888
ssh_manager/invoice_views.py
Normal file
@ -0,0 +1,888 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import JsonResponse
|
||||
from django.contrib import messages
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import models
|
||||
from django.db.models import Sum, F, Value, FloatField, Count
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
from django.core.paginator import Paginator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from .models import Invoice, InvoiceItem, Customer, Project
|
||||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
@login_required
|
||||
def invoices(request):
|
||||
"""Faturalar sayfası - tüm faturaların listesi"""
|
||||
invoices = Invoice.objects.all().select_related('customer').order_by('-issue_date')
|
||||
customers = Customer.objects.filter(is_active=True).order_by('name')
|
||||
|
||||
# Fatura özeti istatistikleri
|
||||
stats = {
|
||||
'total': invoices.count(),
|
||||
'paid': invoices.filter(status='paid').count(),
|
||||
'pending': invoices.filter(status__in=['draft', 'sent']).count(),
|
||||
'overdue': invoices.filter(status='overdue').count(),
|
||||
'total_amount': invoices.aggregate(Sum('total_amount'))['total_amount__sum'] or 0,
|
||||
'paid_amount': invoices.filter(status='paid').aggregate(Sum('total_amount'))['total_amount__sum'] or 0,
|
||||
}
|
||||
|
||||
context = {
|
||||
'invoices': invoices,
|
||||
'customers': customers,
|
||||
'stats': stats,
|
||||
}
|
||||
return render(request, 'ssh_manager/faturalar.html', context)
|
||||
|
||||
@login_required
|
||||
def invoice_detail(request, invoice_id):
|
||||
"""Fatura detay sayfası"""
|
||||
try:
|
||||
invoice = get_object_or_404(Invoice, id=invoice_id)
|
||||
|
||||
# Decimal değerleri güvenli bir şekilde almak için
|
||||
from decimal import Decimal, InvalidOperation, ROUND_DOWN
|
||||
|
||||
# Invoice total_amount değerini güvenli bir şekilde al
|
||||
try:
|
||||
if invoice.total_amount is None:
|
||||
invoice.total_amount = Decimal('0.00')
|
||||
elif isinstance(invoice.total_amount, str):
|
||||
# Virgül varsa noktaya çevir
|
||||
if ',' in invoice.total_amount:
|
||||
invoice.total_amount = invoice.total_amount.replace(',', '.')
|
||||
invoice.total_amount = Decimal(invoice.total_amount).quantize(Decimal('0.01'), rounding=ROUND_DOWN)
|
||||
except (InvalidOperation, TypeError) as e:
|
||||
print(f"Total amount dönüşüm hatası: {str(e)}")
|
||||
invoice.total_amount = Decimal('0.00')
|
||||
|
||||
# Invoice items'ları güvenli bir şekilde al
|
||||
items = invoice.items.all().select_related('project')
|
||||
safe_items = []
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
if item.amount is None:
|
||||
item.amount = Decimal('0.00')
|
||||
elif isinstance(item.amount, str):
|
||||
# Virgül varsa noktaya çevir
|
||||
if ',' in item.amount:
|
||||
item.amount = item.amount.replace(',', '.')
|
||||
item.amount = Decimal(item.amount).quantize(Decimal('0.01'), rounding=ROUND_DOWN)
|
||||
safe_items.append(item)
|
||||
except (InvalidOperation, TypeError) as e:
|
||||
print(f"Item amount dönüşüm hatası: {str(e)}")
|
||||
item.amount = Decimal('0.00')
|
||||
safe_items.append(item)
|
||||
|
||||
# Status ve payment_method display metodlarını önceden çağır
|
||||
try:
|
||||
status_display = invoice.get_status_display()
|
||||
except Exception:
|
||||
status_display = invoice.status
|
||||
|
||||
try:
|
||||
payment_method_display = invoice.get_payment_method_display()
|
||||
except Exception:
|
||||
payment_method_display = invoice.payment_method
|
||||
|
||||
context = {
|
||||
'invoice': invoice,
|
||||
'items': safe_items,
|
||||
'status_display': status_display,
|
||||
'payment_method_display': payment_method_display
|
||||
}
|
||||
return render(request, 'ssh_manager/fatura_detay.html', context)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"Fatura detayı gösterilirken hata oluştu: {str(e)}")
|
||||
print(traceback.format_exc())
|
||||
messages.error(request, f"Fatura görüntülenirken bir hata oluştu: {str(e)}")
|
||||
return redirect('faturalar')
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def create_invoice(request):
|
||||
"""Yeni fatura oluştur"""
|
||||
try:
|
||||
print("---------- YENİ FATURA OLUŞTURMA İSTEĞİ ----------")
|
||||
print("REQUEST METHOD:", request.method)
|
||||
print("CONTENT TYPE:", request.content_type)
|
||||
print("REQUEST HEADERS:", request.headers)
|
||||
|
||||
try:
|
||||
request_body = request.body.decode('utf-8')
|
||||
print("REQUEST BODY:", request_body)
|
||||
|
||||
# JSON data'yı parse et
|
||||
data = json.loads(request_body)
|
||||
print("PARSED DATA:", data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"JSON parse hatası: {str(e)}")
|
||||
return JsonResponse({'success': False, 'message': f'JSON veri hatası: {str(e)}'})
|
||||
|
||||
# Verileri al
|
||||
customer_id = data.get('customer_id')
|
||||
invoice_type = data.get('invoice_type', 'income') # Varsayılan olarak gelir
|
||||
issue_date = data.get('issue_date')
|
||||
due_date = data.get('due_date')
|
||||
payment_method = data.get('payment_method', 'bank_transfer')
|
||||
description = data.get('description', '')
|
||||
project_id = data.get('project_id')
|
||||
amount = data.get('amount', 0)
|
||||
|
||||
print(f"ALANLAR:")
|
||||
print(f"- customer_id: {customer_id}")
|
||||
print(f"- invoice_type: {invoice_type}")
|
||||
print(f"- issue_date: {issue_date}")
|
||||
print(f"- due_date: {due_date}")
|
||||
print(f"- payment_method: {payment_method}")
|
||||
print(f"- description: {description}")
|
||||
print(f"- project_id: {project_id}")
|
||||
print(f"- amount: {amount}")
|
||||
|
||||
# Zorunlu alanları kontrol et
|
||||
if not customer_id:
|
||||
print("HATA: customer_id eksik")
|
||||
return JsonResponse({'success': False, 'message': 'Müşteri seçilmesi zorunludur.'})
|
||||
|
||||
if not issue_date:
|
||||
print("HATA: issue_date eksik")
|
||||
return JsonResponse({'success': False, 'message': 'Düzenleme tarihi zorunludur.'})
|
||||
|
||||
if not due_date:
|
||||
print("HATA: due_date eksik")
|
||||
return JsonResponse({'success': False, 'message': 'Son ödeme tarihi zorunludur.'})
|
||||
|
||||
if not description:
|
||||
print("HATA: description eksik")
|
||||
return JsonResponse({'success': False, 'message': 'Açıklama zorunludur.'})
|
||||
|
||||
try:
|
||||
# Tutarı doğru şekilde kontrol et
|
||||
if amount is None or amount == '':
|
||||
print("HATA: amount boş")
|
||||
return JsonResponse({'success': False, 'message': 'Tutar boş olamaz.'})
|
||||
|
||||
# Virgül olan sayıları nokta ile değiştir
|
||||
if isinstance(amount, str) and ',' in amount:
|
||||
amount = amount.replace(',', '.')
|
||||
|
||||
# Decimal dönüşümü için her zaman string kullan
|
||||
amount_decimal = Decimal(str(amount))
|
||||
|
||||
if amount_decimal <= 0:
|
||||
print("HATA: amount negatif veya sıfır")
|
||||
return JsonResponse({'success': False, 'message': 'Geçerli bir tutar giriniz.'})
|
||||
|
||||
print(f"Tutar başarıyla dönüştürüldü: {amount_decimal}")
|
||||
except Exception as e:
|
||||
print(f"HATA: amount geçersiz: {str(e)}")
|
||||
return JsonResponse({'success': False, 'message': f'Geçerli bir tutar giriniz. Hata: {str(e)}'})
|
||||
|
||||
# Müşteriyi bul
|
||||
try:
|
||||
customer = Customer.objects.get(id=customer_id)
|
||||
print(f"Müşteri bulundu: {customer}")
|
||||
except Customer.DoesNotExist:
|
||||
print(f"HATA: customer_id={customer_id} için müşteri bulunamadı")
|
||||
return JsonResponse({'success': False, 'message': 'Geçersiz müşteri.'})
|
||||
|
||||
# Tarihleri doğru formata çevir
|
||||
try:
|
||||
issue_date_obj = datetime.strptime(issue_date, '%Y-%m-%d').date()
|
||||
due_date_obj = datetime.strptime(due_date, '%Y-%m-%d').date()
|
||||
print(f"Tarihler başarıyla parse edildi: {issue_date_obj}, {due_date_obj}")
|
||||
except ValueError as e:
|
||||
print(f"HATA: Tarih formatı hatalı: {str(e)}")
|
||||
return JsonResponse({'success': False, 'message': 'Geçersiz tarih formatı.'})
|
||||
|
||||
print("Fatura oluşturuluyor...")
|
||||
# Fatura oluştur
|
||||
invoice = Invoice.objects.create(
|
||||
customer=customer,
|
||||
invoice_type=invoice_type,
|
||||
issue_date=issue_date_obj,
|
||||
due_date=due_date_obj,
|
||||
payment_method=payment_method,
|
||||
status='draft'
|
||||
)
|
||||
print(f"Fatura oluşturuldu: ID={invoice.id}, Numara={invoice.invoice_number}, Tip={invoice_type}")
|
||||
|
||||
# Projeyi bul
|
||||
project = None
|
||||
if project_id:
|
||||
try:
|
||||
project = Project.objects.get(id=project_id)
|
||||
print(f"Proje bulundu: {project}")
|
||||
except Project.DoesNotExist:
|
||||
print(f"UYARI: project_id={project_id} için proje bulunamadı")
|
||||
pass
|
||||
|
||||
print("Fatura kalemi oluşturuluyor...")
|
||||
# Tek fatura kalemi olarak ekle - amount_decimal zaten oluşturuldu, tekrar dönüştürmeye gerek yok
|
||||
item = InvoiceItem.objects.create(
|
||||
invoice=invoice,
|
||||
project=project,
|
||||
description=description,
|
||||
amount=amount_decimal
|
||||
)
|
||||
print(f"Fatura kalemi oluşturuldu: ID={item.id}")
|
||||
|
||||
# Başarılı yanıt döndür
|
||||
response_data = {
|
||||
'success': True,
|
||||
'message': 'Fatura başarıyla oluşturuldu.',
|
||||
'invoice_id': invoice.id,
|
||||
'invoice_number': invoice.invoice_number
|
||||
}
|
||||
print("Başarılı yanıt:", response_data)
|
||||
return JsonResponse(response_data)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"KRİTİK HATA: Fatura oluşturulamadı: {str(e)}")
|
||||
print(traceback.format_exc())
|
||||
|
||||
# Daha detaylı hata mesajı oluştur
|
||||
error_type = type(e).__name__
|
||||
error_msg = str(e)
|
||||
|
||||
if error_type == 'InvalidOperation':
|
||||
# Decimal dönüşüm hatası
|
||||
return JsonResponse({'success': False, 'message': f'Geçersiz tutar formatı. Lütfen sayısal bir değer girin.'})
|
||||
else:
|
||||
# Diğer hatalar
|
||||
return JsonResponse({'success': False, 'message': f'Beklenmeyen bir hata oluştu: {error_type} - {error_msg}'})
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def update_invoice(request, invoice_id):
|
||||
"""Fatura güncelle"""
|
||||
try:
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
invoice = get_object_or_404(Invoice, id=invoice_id)
|
||||
data = json.loads(request.body)
|
||||
|
||||
# Temel fatura bilgilerini güncelle
|
||||
if 'issue_date' in data:
|
||||
try:
|
||||
invoice.issue_date = datetime.strptime(data['issue_date'], '%Y-%m-%d').date()
|
||||
except ValueError as e:
|
||||
return JsonResponse({'success': False, 'message': f'Geçersiz düzenleme tarihi formatı: {str(e)}'})
|
||||
|
||||
if 'due_date' in data:
|
||||
try:
|
||||
invoice.due_date = datetime.strptime(data['due_date'], '%Y-%m-%d').date()
|
||||
except ValueError as e:
|
||||
return JsonResponse({'success': False, 'message': f'Geçersiz son ödeme tarihi formatı: {str(e)}'})
|
||||
|
||||
if 'payment_method' in data:
|
||||
invoice.payment_method = data['payment_method']
|
||||
|
||||
if 'status' in data:
|
||||
invoice.status = data['status']
|
||||
|
||||
# Müşteri değişikliği
|
||||
if 'customer_id' in data:
|
||||
try:
|
||||
customer = Customer.objects.get(id=data['customer_id'])
|
||||
invoice.customer = customer
|
||||
except Customer.DoesNotExist:
|
||||
return JsonResponse({'success': False, 'message': 'Geçersiz müşteri.'})
|
||||
|
||||
invoice.save()
|
||||
|
||||
# Fatura açıklama ve tutarını güncelle
|
||||
description = data.get('description')
|
||||
amount = data.get('amount')
|
||||
project_id = data.get('project_id')
|
||||
|
||||
# Mevcut kalemleri temizle
|
||||
invoice.items.all().delete()
|
||||
|
||||
# Projeyi bul
|
||||
project = None
|
||||
if project_id:
|
||||
try:
|
||||
project = Project.objects.get(id=project_id)
|
||||
except Project.DoesNotExist:
|
||||
pass
|
||||
|
||||
# Tutarı güvenli bir şekilde Decimal'e dönüştür
|
||||
if description is not None and amount is not None:
|
||||
try:
|
||||
# Virgül olan sayıları nokta ile değiştir
|
||||
if isinstance(amount, str) and ',' in amount:
|
||||
amount = amount.replace(',', '.')
|
||||
|
||||
# Decimal dönüşümü için her zaman string kullan
|
||||
amount_decimal = Decimal(str(amount))
|
||||
|
||||
if amount_decimal <= 0:
|
||||
return JsonResponse({'success': False, 'message': 'Geçerli bir tutar giriniz (0\'dan büyük olmalı).'})
|
||||
|
||||
# Yeni kalemi ekle
|
||||
InvoiceItem.objects.create(
|
||||
invoice=invoice,
|
||||
project=project,
|
||||
description=description,
|
||||
amount=amount_decimal
|
||||
)
|
||||
except (InvalidOperation, ValueError, TypeError) as e:
|
||||
return JsonResponse({'success': False, 'message': f'Geçersiz tutar formatı: {str(e)}'})
|
||||
else:
|
||||
return JsonResponse({'success': False, 'message': 'Açıklama ve tutar alanları gereklidir.'})
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': 'Fatura başarıyla güncellendi.',
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
print(f"Fatura güncelleme hatası: {str(e)}")
|
||||
print(f"Hata ayrıntıları: {error_trace}")
|
||||
|
||||
# Decimal hatası için özel mesaj
|
||||
if 'decimal.InvalidOperation' in str(e):
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Fatura tutarı geçersiz format içeriyor. Lütfen geçerli bir sayısal değer girin.'
|
||||
})
|
||||
else:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Fatura güncelleme hatası: {str(e)}'
|
||||
})
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["DELETE"])
|
||||
def delete_invoice(request, invoice_id):
|
||||
"""Fatura sil"""
|
||||
try:
|
||||
invoice = get_object_or_404(Invoice, id=invoice_id)
|
||||
invoice_number = invoice.invoice_number
|
||||
invoice.delete()
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'{invoice_number} numaralı fatura silindi.'
|
||||
})
|
||||
except Exception as e:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Fatura silme hatası: {str(e)}'
|
||||
})
|
||||
|
||||
@login_required
|
||||
def get_invoice_details(request, invoice_id):
|
||||
"""Fatura detay bilgilerini JSON olarak döndür - API endpoint"""
|
||||
try:
|
||||
from decimal import Decimal, InvalidOperation
|
||||
import traceback
|
||||
|
||||
print(f"get_invoice_details fonksiyonu çağrıldı: Invoice ID = {invoice_id}")
|
||||
|
||||
try:
|
||||
invoice = get_object_or_404(Invoice, id=invoice_id)
|
||||
print(f"Invoice {invoice_id} bulundu")
|
||||
except Exception as e:
|
||||
print(f"Invoice {invoice_id} bulunamadı: {str(e)}")
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Fatura bulunamadı (ID: {invoice_id})'
|
||||
}, status=404)
|
||||
|
||||
# Decimal değerlerini güvenli bir şekilde dizeye dönüştür
|
||||
def safe_decimal_to_str(value):
|
||||
if value is None:
|
||||
return "0.00"
|
||||
try:
|
||||
# Eğer string ise ve virgül içeriyorsa, nokta ile değiştir
|
||||
if isinstance(value, str) and ',' in value:
|
||||
value = value.replace(',', '.')
|
||||
|
||||
# Decimal'e dönüştür
|
||||
decimal_value = Decimal(str(value))
|
||||
return str(decimal_value)
|
||||
except (InvalidOperation, ValueError, TypeError) as e:
|
||||
print(f"Decimal dönüşüm hatası: {value} - {str(e)}")
|
||||
return "0.00"
|
||||
|
||||
# Müşteri bilgisini güvenli bir şekilde al
|
||||
customer_data = {'id': 0, 'name': 'Bilinmeyen Müşteri'}
|
||||
try:
|
||||
if invoice.customer:
|
||||
customer_data = {
|
||||
'id': invoice.customer.id,
|
||||
'name': invoice.customer.name # Direkt olarak name özelliğini kullan
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Müşteri bilgisi alınamadı: {str(e)}")
|
||||
|
||||
# Temel fatura bilgilerini güvenli şekilde al
|
||||
invoice_data = {
|
||||
'id': invoice.id,
|
||||
'invoice_number': invoice.invoice_number or f"FATURA-{invoice.id}",
|
||||
'status': invoice.status or 'draft',
|
||||
'payment_method': invoice.payment_method or 'bank_transfer',
|
||||
'total_amount': safe_decimal_to_str(invoice.total_amount),
|
||||
'notes': invoice.notes or '',
|
||||
'payment_notes': invoice.payment_notes or '',
|
||||
'customer': customer_data,
|
||||
'items': []
|
||||
}
|
||||
|
||||
# Tarihleri güvenli şekilde ekle
|
||||
try:
|
||||
invoice_data['issue_date'] = invoice.issue_date.strftime('%Y-%m-%d') if invoice.issue_date else ""
|
||||
except Exception as e:
|
||||
print(f"Issue date formatlanırken hata: {str(e)}")
|
||||
invoice_data['issue_date'] = ""
|
||||
|
||||
try:
|
||||
invoice_data['due_date'] = invoice.due_date.strftime('%Y-%m-%d') if invoice.due_date else ""
|
||||
except Exception as e:
|
||||
print(f"Due date formatlanırken hata: {str(e)}")
|
||||
invoice_data['due_date'] = ""
|
||||
|
||||
try:
|
||||
invoice_data['created_at'] = invoice.created_at.strftime('%Y-%m-%d %H:%M:%S') if invoice.created_at else ""
|
||||
invoice_data['updated_at'] = invoice.updated_at.strftime('%Y-%m-%d %H:%M:%S') if invoice.updated_at else ""
|
||||
except Exception as e:
|
||||
print(f"Oluşturma/güncelleme tarihi formatlanırken hata: {str(e)}")
|
||||
invoice_data['created_at'] = ""
|
||||
invoice_data['updated_at'] = ""
|
||||
|
||||
# Fatura kalemlerini güvenli şekilde al
|
||||
try:
|
||||
items = invoice.items.all()
|
||||
print(f"{len(items)} kalem bulundu")
|
||||
|
||||
# Kalem bulunamadıysa bile en azından bir tane boş kalem ekle
|
||||
if not items.exists():
|
||||
print("Fatura kalemi bulunamadı, varsayılan boş kalem ekleniyor")
|
||||
invoice_data['items'].append({
|
||||
'id': 0,
|
||||
'description': '',
|
||||
'amount': '0.00',
|
||||
'project_id': None,
|
||||
'project_name': None
|
||||
})
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
# Proje bilgisini güvenli bir şekilde al
|
||||
project_id = None
|
||||
project_name = None
|
||||
|
||||
try:
|
||||
if item.project:
|
||||
project_id = item.project.id
|
||||
project_name = item.project.name
|
||||
except Exception as proj_err:
|
||||
print(f"Proje bilgisi alınamadı: {str(proj_err)}")
|
||||
|
||||
# Kalem açıklama bilgisini mutlaka ekle
|
||||
description = item.description
|
||||
if not description or description.strip() == '':
|
||||
description = 'Açıklama yok'
|
||||
print(f"Kalem {item.id} için açıklama bulunamadı, varsayılan değer kullanıldı")
|
||||
else:
|
||||
print(f"Kalem {item.id} açıklaması: '{description}'")
|
||||
|
||||
# Tutarı güvenli şekilde al
|
||||
amount = safe_decimal_to_str(item.amount)
|
||||
print(f"Kalem {item.id} tutarı: {amount}")
|
||||
|
||||
item_data = {
|
||||
'id': item.id,
|
||||
'description': description,
|
||||
'amount': amount,
|
||||
'project_id': project_id,
|
||||
'project_name': project_name
|
||||
}
|
||||
invoice_data['items'].append(item_data)
|
||||
print(f"Kalem eklendi: {item_data}")
|
||||
except Exception as item_err:
|
||||
print(f"Kalem serileştirilirken hata: {str(item_err)}")
|
||||
# Hataya neden olan kalemi atla ve devam et
|
||||
except Exception as items_err:
|
||||
print(f"Kalemler alınırken hata: {str(items_err)}")
|
||||
|
||||
# Başarılı yanıt döndür
|
||||
print("Fatura detayları başarıyla alındı")
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'invoice': invoice_data
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_trace = traceback.format_exc()
|
||||
print(f"Fatura detayları alınamadı hata: {str(e)}")
|
||||
print(f"Hata ayrıntıları: {error_trace}")
|
||||
|
||||
# Özel hata mesajları
|
||||
error_message = str(e)
|
||||
|
||||
if 'decimal.InvalidOperation' in error_message:
|
||||
error_message = 'Fatura tutarı geçersiz format içeriyor.'
|
||||
elif 'DoesNotExist' in error_message:
|
||||
error_message = f'Fatura (ID: {invoice_id}) bulunamadı.'
|
||||
elif 'NoneType' in error_message:
|
||||
error_message = 'Bazı veriler eksik veya geçersiz.'
|
||||
|
||||
# Daha fazla tanısal bilgi ekle
|
||||
debug_info = f"[Hata tipi: {type(e).__name__}]"
|
||||
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Fatura detayları alınamadı: {error_message} {debug_info}'
|
||||
}, status=500)
|
||||
|
||||
@login_required
|
||||
def get_projects_by_customer(request, customer_id):
|
||||
"""Müşteriye ait projeleri getir"""
|
||||
try:
|
||||
projects = Project.objects.filter(customer_id=customer_id)
|
||||
data = [{
|
||||
'id': project.id,
|
||||
'name': project.name,
|
||||
'folder_name': project.folder_name,
|
||||
'url': project.url
|
||||
} for project in projects]
|
||||
|
||||
return JsonResponse({'success': True, 'projects': data})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'message': str(e)})
|
||||
|
||||
@login_required
|
||||
def invoice_reports(request):
|
||||
"""Fatura raporları sayfası"""
|
||||
# Tarih filtreleri
|
||||
today = timezone.now().date()
|
||||
start_date = request.GET.get('start_date', (today - timedelta(days=30)).strftime('%Y-%m-%d'))
|
||||
end_date = request.GET.get('end_date', today.strftime('%Y-%m-%d'))
|
||||
|
||||
# String tarihleri datetime'a çevir
|
||||
try:
|
||||
start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
|
||||
end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
start_date = today - timedelta(days=30)
|
||||
end_date = today
|
||||
|
||||
# Filtreler
|
||||
invoices = Invoice.objects.filter(issue_date__range=[start_date, end_date])
|
||||
|
||||
# İstatistikler
|
||||
stats = {
|
||||
'total_invoices': invoices.count(),
|
||||
'total_amount': invoices.aggregate(Sum('total_amount'))['total_amount__sum'] or 0,
|
||||
'paid_amount': invoices.filter(status='paid').aggregate(Sum('total_amount'))['total_amount__sum'] or 0,
|
||||
'unpaid_amount': invoices.filter(status__in=['draft', 'sent', 'overdue']).aggregate(Sum('total_amount'))['total_amount__sum'] or 0,
|
||||
'paid_count': invoices.filter(status='paid').count(),
|
||||
'unpaid_count': invoices.filter(status__in=['draft', 'sent', 'overdue']).count(),
|
||||
'overdue_count': invoices.filter(status='overdue').count(),
|
||||
}
|
||||
|
||||
# Müşterilere göre dağılım
|
||||
customers_data = invoices.values('customer__name').annotate(
|
||||
total=Sum('total_amount')
|
||||
).order_by('-total')[:10]
|
||||
|
||||
# Durumlara göre dağılım
|
||||
status_data = invoices.values('status').annotate(
|
||||
count=models.Count('id'),
|
||||
total=Sum('total_amount')
|
||||
).order_by('status')
|
||||
|
||||
context = {
|
||||
'stats': stats,
|
||||
'customers_data': customers_data,
|
||||
'status_data': status_data,
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'today': today.strftime('%Y-%m-%d'),
|
||||
}
|
||||
|
||||
return render(request, 'ssh_manager/fatura_raporlari.html', context)
|
||||
|
||||
@login_required
|
||||
def test_fatura(request):
|
||||
"""Test fatura sayfası"""
|
||||
customers = Customer.objects.filter(is_active=True).order_by('name')
|
||||
|
||||
context = {
|
||||
'customers': customers
|
||||
}
|
||||
return render(request, 'ssh_manager/test_fatura.html', context)
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def update_invoice_status(request, invoice_id):
|
||||
"""Fatura durumunu günceller"""
|
||||
try:
|
||||
# JSON veriyi parse et
|
||||
data = json.loads(request.body)
|
||||
new_status = data.get('status')
|
||||
|
||||
if not new_status:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Durum bilgisi gerekli'
|
||||
}, status=400)
|
||||
|
||||
# Geçerli durum kontrolü
|
||||
valid_statuses = ['draft', 'sent', 'paid', 'overdue', 'cancelled']
|
||||
if new_status not in valid_statuses:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Geçersiz durum: {new_status}'
|
||||
}, status=400)
|
||||
|
||||
# Faturayı bul ve güncelle
|
||||
invoice = get_object_or_404(Invoice, id=invoice_id)
|
||||
old_status = invoice.status
|
||||
invoice.status = new_status
|
||||
invoice.save()
|
||||
|
||||
# Log mesajı
|
||||
print(f"Fatura {invoice_id} durumu '{old_status}' -> '{new_status}' olarak güncellendi")
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'Fatura durumu başarıyla güncellendi',
|
||||
'old_status': old_status,
|
||||
'new_status': new_status
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Geçersiz JSON verisi'
|
||||
}, status=400)
|
||||
except Exception as e:
|
||||
print(f"Fatura durum güncelleme hatası: {str(e)}")
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Durum güncellenirken hata oluştu: {str(e)}'
|
||||
}, status=500)
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["POST"])
|
||||
def bulk_update_invoice_status(request):
|
||||
"""Toplu fatura durum güncelleme"""
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
invoice_ids = data.get('invoice_ids', [])
|
||||
new_status = data.get('status')
|
||||
|
||||
if not invoice_ids or not new_status:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Fatura ID\'leri ve durum bilgisi gerekli'
|
||||
}, status=400)
|
||||
|
||||
# Geçerli durum kontrolü
|
||||
valid_statuses = ['draft', 'sent', 'paid', 'overdue', 'cancelled']
|
||||
if new_status not in valid_statuses:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Geçersiz durum: {new_status}'
|
||||
}, status=400)
|
||||
|
||||
# Faturaları toplu güncelle
|
||||
updated_count = Invoice.objects.filter(id__in=invoice_ids).update(status=new_status)
|
||||
|
||||
print(f"{updated_count} fatura durumu '{new_status}' olarak güncellendi")
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'{updated_count} fatura durumu başarıyla güncellendi',
|
||||
'updated_count': updated_count,
|
||||
'new_status': new_status
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Geçersiz JSON verisi'
|
||||
}, status=400)
|
||||
except Exception as e:
|
||||
print(f"Toplu durum güncelleme hatası: {str(e)}")
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Durumlar güncellenirken hata oluştu: {str(e)}'
|
||||
}, status=500)
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["DELETE"])
|
||||
def bulk_delete_invoices(request):
|
||||
"""Toplu fatura silme"""
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
invoice_ids = data.get('invoice_ids', [])
|
||||
|
||||
if not invoice_ids:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Fatura ID\'leri gerekli'
|
||||
}, status=400)
|
||||
|
||||
# Faturaları toplu sil
|
||||
deleted_count, _ = Invoice.objects.filter(id__in=invoice_ids).delete()
|
||||
|
||||
print(f"{deleted_count} fatura silindi")
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': f'{deleted_count} fatura başarıyla silindi',
|
||||
'deleted_count': deleted_count
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': 'Geçersiz JSON verisi'
|
||||
}, status=400)
|
||||
except Exception as e:
|
||||
print(f"Toplu silme hatası: {str(e)}")
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'message': f'Faturalar silinirken hata oluştu: {str(e)}'
|
||||
}, status=500)
|
||||
|
||||
@login_required
|
||||
def profit_loss_report(request):
|
||||
"""Kar/Zarar raporu"""
|
||||
# Tarih filtreleri
|
||||
today = timezone.now().date()
|
||||
start_date = request.GET.get('start_date', (today - timedelta(days=30)).strftime('%Y-%m-%d'))
|
||||
end_date = request.GET.get('end_date', today.strftime('%Y-%m-%d'))
|
||||
customer_id = request.GET.get('customer_id', '')
|
||||
|
||||
# String tarihleri datetime'a çevir
|
||||
try:
|
||||
start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
|
||||
end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
|
||||
except ValueError:
|
||||
start_date = today - timedelta(days=30)
|
||||
end_date = today
|
||||
|
||||
# Temel sorgu
|
||||
invoices = Invoice.objects.filter(
|
||||
issue_date__range=[start_date, end_date],
|
||||
status='paid' # Sadece ödenmiş faturalar
|
||||
)
|
||||
|
||||
# Müşteri filtresi
|
||||
if customer_id:
|
||||
try:
|
||||
customer = Customer.objects.get(id=customer_id)
|
||||
invoices = invoices.filter(customer=customer)
|
||||
except Customer.DoesNotExist:
|
||||
customer = None
|
||||
else:
|
||||
customer = None
|
||||
|
||||
# Gelir ve gider hesaplamaları
|
||||
income_invoices = invoices.filter(invoice_type='income')
|
||||
expense_invoices = invoices.filter(invoice_type='expense')
|
||||
|
||||
total_income = income_invoices.aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
total_expense = expense_invoices.aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
net_profit = total_income - total_expense
|
||||
|
||||
# Genel özet
|
||||
summary = {
|
||||
'total_income': total_income,
|
||||
'total_expense': total_expense,
|
||||
'net_profit': net_profit,
|
||||
'profit_margin': (net_profit / total_income * 100) if total_income > 0 else 0,
|
||||
'income_count': income_invoices.count(),
|
||||
'expense_count': expense_invoices.count(),
|
||||
}
|
||||
|
||||
# Müşteri bazında kar/zarar (sadece genel rapor için)
|
||||
customer_stats = []
|
||||
if not customer_id:
|
||||
customers = Customer.objects.filter(
|
||||
invoices__issue_date__range=[start_date, end_date],
|
||||
invoices__status='paid'
|
||||
).distinct()
|
||||
|
||||
for cust in customers:
|
||||
cust_invoices = invoices.filter(customer=cust)
|
||||
cust_income = cust_invoices.filter(invoice_type='income').aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
cust_expense = cust_invoices.filter(invoice_type='expense').aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
cust_profit = cust_income - cust_expense
|
||||
|
||||
customer_stats.append({
|
||||
'customer': cust,
|
||||
'income': cust_income,
|
||||
'expense': cust_expense,
|
||||
'profit': cust_profit,
|
||||
'profit_margin': (cust_profit / cust_income * 100) if cust_income > 0 else 0,
|
||||
})
|
||||
|
||||
# Kar'a göre sırala
|
||||
customer_stats.sort(key=lambda x: x['profit'], reverse=True)
|
||||
|
||||
# Aylık trend analizi
|
||||
monthly_data = []
|
||||
current_date = start_date.replace(day=1) # Ay başına ayarla
|
||||
|
||||
while current_date <= end_date:
|
||||
month_end = current_date.replace(day=28) + timedelta(days=4) # Sonraki ay
|
||||
month_end = month_end - timedelta(days=month_end.day) # Ayın son günü
|
||||
|
||||
month_invoices = invoices.filter(
|
||||
issue_date__range=[current_date, month_end]
|
||||
)
|
||||
|
||||
month_income = month_invoices.filter(invoice_type='income').aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
month_expense = month_invoices.filter(invoice_type='expense').aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
|
||||
monthly_data.append({
|
||||
'month': current_date.strftime('%Y-%m'),
|
||||
'month_name': current_date.strftime('%B %Y'),
|
||||
'income': month_income,
|
||||
'expense': month_expense,
|
||||
'profit': month_income - month_expense,
|
||||
})
|
||||
|
||||
# Sonraki aya geç
|
||||
if current_date.month == 12:
|
||||
current_date = current_date.replace(year=current_date.year + 1, month=1)
|
||||
else:
|
||||
current_date = current_date.replace(month=current_date.month + 1)
|
||||
|
||||
# Son faturalar (gelir/gider ayrımı ile)
|
||||
recent_income = income_invoices.order_by('-issue_date')[:5]
|
||||
recent_expense = expense_invoices.order_by('-issue_date')[:5]
|
||||
|
||||
context = {
|
||||
'summary': summary,
|
||||
'customer_stats': customer_stats,
|
||||
'monthly_data': monthly_data,
|
||||
'recent_income': recent_income,
|
||||
'recent_expense': recent_expense,
|
||||
'start_date': start_date.strftime('%Y-%m-%d'),
|
||||
'end_date': end_date.strftime('%Y-%m-%d'),
|
||||
'today': today.strftime('%Y-%m-%d'),
|
||||
'selected_customer': customer,
|
||||
'customers': Customer.objects.filter(is_active=True).order_by('name'),
|
||||
}
|
||||
|
||||
return render(request, 'ssh_manager/kar_zarar_raporu.html', context)
|
||||
Reference in New Issue
Block a user