Files
hostpanel/ssh_manager/invoice_views.py
ilkeral f4ee7a2d0b yeni
2025-08-08 07:24:25 +03:00

889 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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': 'ı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': 'ı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 = 'ıklama yok'
print(f"Kalem {item.id} için açıklama bulunamadı, varsayılan değer kullanıldı")
else:
print(f"Kalem {item.id}ı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)