205 lines
7.9 KiB
Python
205 lines
7.9 KiB
Python
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
|
|
class UserManager(BaseUserManager):
|
|
"""Define a model manager for User model with no username field."""
|
|
|
|
use_in_migrations = True
|
|
|
|
def _create_user(self, email, password, **extra_fields):
|
|
"""Create and save a User with the given email and password."""
|
|
if not email:
|
|
raise ValueError('The given email must be set')
|
|
email = self.normalize_email(email)
|
|
user = self.model(email=email, **extra_fields)
|
|
user.set_password(password)
|
|
user.save(using=self._db)
|
|
return user
|
|
|
|
def create_user(self, email, password=None, **extra_fields):
|
|
"""Create and save a regular User with the given email and password."""
|
|
extra_fields.setdefault('is_staff', False)
|
|
extra_fields.setdefault('is_superuser', False)
|
|
return self._create_user(email, password, **extra_fields)
|
|
|
|
def create_superuser(self, email, password, **extra_fields):
|
|
"""Create and save a SuperUser with the given email and password."""
|
|
extra_fields.setdefault('is_staff', True)
|
|
extra_fields.setdefault('is_superuser', True)
|
|
|
|
if extra_fields.get('is_staff') is not True:
|
|
raise ValueError('Superuser must have is_staff=True.')
|
|
if extra_fields.get('is_superuser') is not True:
|
|
raise ValueError('Superuser must have is_superuser=True.')
|
|
|
|
return self._create_user(email, password, **extra_fields)
|
|
|
|
class Tenant(models.Model):
|
|
name = models.CharField(max_length=255)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class User(AbstractUser):
|
|
username = None
|
|
email = models.EmailField(unique=True)
|
|
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, null=True, blank=True)
|
|
role = models.CharField(max_length=100, blank=True)
|
|
|
|
USERNAME_FIELD = 'email'
|
|
REQUIRED_FIELDS = []
|
|
|
|
objects = UserManager()
|
|
|
|
class BaseModel(models.Model):
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
is_deleted = models.BooleanField(default=False)
|
|
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='%(class)s_created_by')
|
|
updated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='%(class)s_updated_by')
|
|
deleted_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='%(class)s_deleted_by')
|
|
restored_at = models.DateTimeField(null=True, blank=True)
|
|
restored_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='%(class)s_restored_by')
|
|
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
def delete(self, user=None, *args, **kwargs):
|
|
self.is_deleted = True
|
|
self.deleted_at = timezone.now()
|
|
if user:
|
|
self.deleted_by = user
|
|
self.save()
|
|
ActivityLog.objects.create(
|
|
actor=user,
|
|
action=f'deleted a {self.__class__.__name__}',
|
|
content_object=self,
|
|
details={'id': self.id, 'name': str(self)}
|
|
)
|
|
|
|
def restore(self, user=None, *args, **kwargs):
|
|
self.is_deleted = False
|
|
self.deleted_at = None
|
|
self.deleted_by = None
|
|
self.restored_at = timezone.now()
|
|
if user:
|
|
self.restored_by = user
|
|
self.save()
|
|
ActivityLog.objects.create(
|
|
actor=user,
|
|
action=f'restored a {self.__class__.__name__}',
|
|
content_object=self,
|
|
details={'id': self.id, 'name': str(self)}
|
|
)
|
|
|
|
class ActivityLog(models.Model):
|
|
actor = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
|
|
action = models.CharField(max_length=255)
|
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
|
object_id = models.PositiveIntegerField()
|
|
content_object = GenericForeignKey('content_type', 'object_id')
|
|
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
details = models.JSONField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-timestamp']
|
|
|
|
def get_action_badge(self):
|
|
if 'deleted' in self.action:
|
|
return ('bg-danger', 'deleted')
|
|
elif 'restored' in self.action:
|
|
return ('bg-success', 'restored')
|
|
elif 'created' in self.action:
|
|
return ('bg-primary', 'created')
|
|
elif 'updated' in self.action:
|
|
return ('bg-info', 'updated')
|
|
else:
|
|
action_verb = self.action.split(' ')[0]
|
|
return ('bg-secondary', action_verb)
|
|
|
|
class Customer(BaseModel):
|
|
STATUS_CHOICES = (
|
|
('active', 'Active'),
|
|
('inactive', 'Inactive'),
|
|
('lead', 'Lead'),
|
|
)
|
|
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
|
|
name = models.CharField(max_length=255)
|
|
email = models.EmailField()
|
|
phone = models.CharField(max_length=50, blank=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active', db_index=True)
|
|
owner = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='customers')
|
|
|
|
class Meta:
|
|
unique_together = ('tenant', 'email')
|
|
indexes = [
|
|
models.Index(fields=['tenant', 'status']),
|
|
models.Index(fields=['created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Lead(BaseModel):
|
|
STATUS_CHOICES = (
|
|
('new', 'New'),
|
|
('contacted', 'Contacted'),
|
|
('qualified', 'Qualified'),
|
|
('unqualified', 'Unqualified'),
|
|
)
|
|
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='leads')
|
|
source = models.CharField(max_length=255, blank=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='new', db_index=True)
|
|
assigned_to = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='leads')
|
|
|
|
def __str__(self):
|
|
return f"Lead for {self.customer.name}"
|
|
|
|
class Opportunity(BaseModel):
|
|
STAGE_CHOICES = (
|
|
('prospecting', 'Prospecting'),
|
|
('qualification', 'Qualification'),
|
|
('proposal', 'Proposal'),
|
|
('negotiation', 'Negotiation'),
|
|
('closed_won', 'Closed Won'),
|
|
('closed_lost', 'Closed Lost'),
|
|
)
|
|
name = models.CharField(max_length=255, blank=True, null=True)
|
|
lead = models.ForeignKey(Lead, on_delete=models.CASCADE, related_name='opportunities')
|
|
value = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
|
stage = models.CharField(max_length=20, choices=STAGE_CHOICES, default='prospecting', db_index=True)
|
|
probability = models.FloatField(null=True, blank=True)
|
|
close_date = models.DateField(null=True, blank=True)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class ContactHistory(BaseModel):
|
|
INTERACTION_CHOICES = (
|
|
('email', 'Email'),
|
|
('phone', 'Phone Call'),
|
|
('meeting', 'Meeting'),
|
|
('note', 'Note'),
|
|
)
|
|
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='contact_history')
|
|
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
|
|
note = models.TextField()
|
|
interaction_type = models.CharField(max_length=20, choices=INTERACTION_CHOICES, default='note')
|
|
|
|
def __str__(self):
|
|
return f"Contact with {self.customer.name} on {self.created_at.date()}"
|
|
|
|
class Note(BaseModel):
|
|
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
|
object_id = models.PositiveIntegerField()
|
|
content_object = GenericForeignKey('content_type', 'object_id')
|
|
content = models.TextField()
|
|
|
|
def __str__(self):
|
|
return f"Note for {self.content_object}" |