yeni
This commit is contained in:
@ -3,6 +3,7 @@ import logging
|
||||
from .ssh_client import SSHManager
|
||||
from django.core.validators import URLValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
from .system_settings import SystemSettings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -104,9 +105,10 @@ class Project(models.Model):
|
||||
folder_name = models.CharField(max_length=100, verbose_name='Klasör Adı')
|
||||
ssh_credential = models.ForeignKey(SSHCredential, on_delete=models.CASCADE)
|
||||
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name='Müşteri', null=True, blank=True)
|
||||
url = models.CharField(max_length=255, null=True, blank=True)
|
||||
url = models.TextField(null=True, blank=True, verbose_name='Site URL')
|
||||
disk_usage = models.CharField(max_length=20, null=True, blank=True)
|
||||
last_backup = models.DateTimeField(null=True, blank=True, verbose_name='Son Yedekleme')
|
||||
host_renewal_date = models.DateField(null=True, blank=True, verbose_name='Host Yenileme Tarihi')
|
||||
meta_key = models.CharField(max_length=32, null=True, blank=True, verbose_name='Meta Key', help_text='Site aktiflik kontrolü için benzersiz anahtar')
|
||||
is_site_active = models.BooleanField(default=False, verbose_name='Site Aktif')
|
||||
last_site_check = models.DateTimeField(null=True, blank=True, verbose_name='Son Site Kontrolü')
|
||||
@ -124,19 +126,21 @@ class Project(models.Model):
|
||||
if not self.meta_key:
|
||||
self.generate_meta_key()
|
||||
self.save()
|
||||
return f'<meta name="site-verification" content="{self.meta_key}">'
|
||||
|
||||
# Farklı meta tag formatları
|
||||
meta_tags = [
|
||||
f'<meta name="site-verification" content="{self.meta_key}">',
|
||||
f'<meta name="site-verify" content="{self.meta_key}">',
|
||||
f'<meta name="verification" content="{self.meta_key}">'
|
||||
]
|
||||
|
||||
# HTML yorum içinde meta key
|
||||
comment_tag = f'<!-- site-verification: {self.meta_key} -->'
|
||||
|
||||
# Tüm formatları birleştir
|
||||
return meta_tags[0], meta_tags, comment_tag
|
||||
|
||||
def clean(self):
|
||||
# URL formatını kontrol et
|
||||
if self.url:
|
||||
# URL'den http/https ve www. kısımlarını temizle
|
||||
cleaned_url = self.url.lower()
|
||||
for prefix in ['http://', 'https://', 'www.']:
|
||||
if cleaned_url.startswith(prefix):
|
||||
cleaned_url = cleaned_url[len(prefix):]
|
||||
# Sondaki / işaretini kaldır
|
||||
cleaned_url = cleaned_url.rstrip('/')
|
||||
self.url = cleaned_url
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
@ -175,6 +179,131 @@ class Project(models.Model):
|
||||
verbose_name_plural = "Projeler"
|
||||
ordering = ['-created_at']
|
||||
|
||||
class Invoice(models.Model):
|
||||
INVOICE_STATUS = (
|
||||
('draft', 'Taslak'),
|
||||
('sent', 'Gönderildi'),
|
||||
('paid', 'Ödendi'),
|
||||
('overdue', 'Gecikti'),
|
||||
('cancelled', 'İptal Edildi'),
|
||||
)
|
||||
|
||||
PAYMENT_METHODS = (
|
||||
('bank_transfer', 'Banka Havalesi'),
|
||||
('credit_card', 'Kredi Kartı'),
|
||||
('cash', 'Nakit'),
|
||||
('other', 'Diğer'),
|
||||
)
|
||||
|
||||
INVOICE_TYPES = (
|
||||
('income', 'Gelir'),
|
||||
('expense', 'Gider'),
|
||||
)
|
||||
|
||||
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='invoices', verbose_name='Müşteri')
|
||||
invoice_number = models.CharField(max_length=50, unique=True, verbose_name='Fatura No')
|
||||
invoice_type = models.CharField(max_length=10, choices=INVOICE_TYPES, default='income', verbose_name='Fatura Tipi')
|
||||
issue_date = models.DateField(verbose_name='Düzenleme Tarihi')
|
||||
due_date = models.DateField(verbose_name='Son Ödeme Tarihi')
|
||||
status = models.CharField(max_length=20, choices=INVOICE_STATUS, default='draft', verbose_name='Durum')
|
||||
payment_method = models.CharField(max_length=20, choices=PAYMENT_METHODS, default='bank_transfer', verbose_name='Ödeme Yöntemi')
|
||||
total_amount = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='Toplam Tutar')
|
||||
notes = models.TextField(blank=True, null=True, verbose_name='Notlar')
|
||||
payment_notes = models.TextField(blank=True, null=True, verbose_name='Ödeme Notları')
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name='Oluşturma Tarihi')
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name='Güncelleme Tarihi')
|
||||
|
||||
def __str__(self):
|
||||
return f"Fatura #{self.invoice_number} - {self.customer.get_display_name()}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.invoice_number:
|
||||
# Otomatik fatura numarası oluştur (F-YIL-AYGUN-XXXX)
|
||||
from datetime import datetime
|
||||
year = datetime.now().strftime('%Y')
|
||||
day_month = datetime.now().strftime('%d%m')
|
||||
|
||||
# Son fatura numarasını bul
|
||||
last_invoice = Invoice.objects.filter(
|
||||
invoice_number__startswith=f'F-{year}'
|
||||
).order_by('-invoice_number').first()
|
||||
|
||||
if last_invoice:
|
||||
try:
|
||||
last_num = int(last_invoice.invoice_number.split('-')[-1])
|
||||
new_num = last_num + 1
|
||||
except (ValueError, IndexError):
|
||||
new_num = 1
|
||||
else:
|
||||
new_num = 1
|
||||
|
||||
self.invoice_number = f'F-{year}-{day_month}-{new_num:04d}'
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Fatura"
|
||||
verbose_name_plural = "Faturalar"
|
||||
ordering = ['-issue_date', '-id']
|
||||
|
||||
class InvoiceItem(models.Model):
|
||||
invoice = models.ForeignKey(Invoice, related_name='items', on_delete=models.CASCADE, verbose_name='Fatura')
|
||||
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='İlişkili Proje')
|
||||
description = models.CharField(max_length=255, verbose_name='Açıklama')
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Tutar')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.description} - {self.invoice.invoice_number}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
self.update_invoice_total()
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
invoice = self.invoice
|
||||
super().delete(*args, **kwargs)
|
||||
# Fatura silindiğinde tutarı güncelle
|
||||
self.update_invoice_total()
|
||||
|
||||
def update_invoice_total(self):
|
||||
"""Fatura toplam tutarını güncelle"""
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
invoice = self.invoice
|
||||
items = invoice.items.all()
|
||||
|
||||
# Toplam tutar - güvenli dönüşümle hesapla
|
||||
total = Decimal('0.00')
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
# Eğer item.amount None ise veya geçersizse, 0 olarak kabul et
|
||||
if item.amount is None:
|
||||
continue
|
||||
|
||||
# String ise decimal'e çevir
|
||||
if isinstance(item.amount, str):
|
||||
if ',' in item.amount:
|
||||
item.amount = item.amount.replace(',', '.')
|
||||
item_amount = Decimal(item.amount)
|
||||
else:
|
||||
item_amount = Decimal(str(item.amount))
|
||||
|
||||
total += item_amount
|
||||
except (InvalidOperation, ValueError, TypeError) as e:
|
||||
print(f"Invoice total hesaplama hatası: {str(e)}")
|
||||
# Hatalı öğeyi atla
|
||||
continue
|
||||
|
||||
# Fatura tutarını güncelle
|
||||
invoice.total_amount = total
|
||||
invoice.save()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Fatura Kalemi"
|
||||
verbose_name_plural = "Fatura Kalemleri"
|
||||
ordering = ['id']
|
||||
|
||||
class SSHLog(models.Model):
|
||||
LOG_TYPES = (
|
||||
('connection', 'Bağlantı Kontrolü'),
|
||||
@ -195,4 +324,65 @@ class SSHLog(models.Model):
|
||||
return f"{self.ssh_credential.hostname} - {self.log_type} - {self.created_at}"
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
ordering = ['-created_at']
|
||||
|
||||
class Backup(models.Model):
|
||||
BACKUP_STATUS = (
|
||||
('running', 'Devam Ediyor'),
|
||||
('completed', 'Tamamlandı'),
|
||||
('failed', 'Başarısız'),
|
||||
('cancelled', 'İptal Edildi'),
|
||||
)
|
||||
|
||||
BACKUP_TYPE = (
|
||||
('manual', 'Manuel'),
|
||||
('auto', 'Otomatik'),
|
||||
('scheduled', 'Zamanlanmış'),
|
||||
)
|
||||
|
||||
project = models.ForeignKey(Project, on_delete=models.CASCADE, verbose_name='Proje')
|
||||
backup_type = models.CharField(max_length=20, choices=BACKUP_TYPE, default='manual', verbose_name='Yedekleme Tipi')
|
||||
status = models.CharField(max_length=20, choices=BACKUP_STATUS, default='running', verbose_name='Durum')
|
||||
start_time = models.DateTimeField(auto_now_add=True, verbose_name='Başlangıç Zamanı')
|
||||
end_time = models.DateTimeField(null=True, blank=True, verbose_name='Bitiş Zamanı')
|
||||
file_path = models.CharField(max_length=500, null=True, blank=True, verbose_name='Dosya Yolu')
|
||||
file_size = models.BigIntegerField(null=True, blank=True, verbose_name='Dosya Boyutu') # BigIntegerField kullanarak büyük dosya boyutlarını destekle
|
||||
error_message = models.TextField(null=True, blank=True, verbose_name='Hata Mesajı')
|
||||
notes = models.TextField(null=True, blank=True, verbose_name='Notlar')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.project.name} - {self.start_time.strftime('%d.%m.%Y %H:%M')}"
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
"""Yedekleme süresini hesapla"""
|
||||
if self.end_time:
|
||||
duration = self.end_time - self.start_time
|
||||
total_seconds = int(duration.total_seconds())
|
||||
hours = total_seconds // 3600
|
||||
minutes = (total_seconds % 3600) // 60
|
||||
seconds = total_seconds % 60
|
||||
|
||||
if hours > 0:
|
||||
return f"{hours}s {minutes}d {seconds}s"
|
||||
elif minutes > 0:
|
||||
return f"{minutes}d {seconds}s"
|
||||
else:
|
||||
return f"{seconds}s"
|
||||
return "-"
|
||||
|
||||
@property
|
||||
def status_badge_class(self):
|
||||
"""Durum badge CSS sınıfı"""
|
||||
status_classes = {
|
||||
'running': 'bg-info',
|
||||
'completed': 'bg-success',
|
||||
'failed': 'bg-danger',
|
||||
'cancelled': 'bg-warning'
|
||||
}
|
||||
return status_classes.get(self.status, 'bg-secondary')
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Yedekleme"
|
||||
verbose_name_plural = "Yedeklemeler"
|
||||
ordering = ['-start_time']
|
||||
Reference in New Issue
Block a user