311 lines
12 KiB
Python
311 lines
12 KiB
Python
import uuid
|
|
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
|
|
|
|
class Profile(models.Model):
|
|
"""Extension of the Django User model"""
|
|
ROLE_CHOICES = (
|
|
('admin', 'Admin'),
|
|
('team_leader', 'Team Leader'),
|
|
('team_member', 'Team Member'),
|
|
)
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
|
first_name = models.CharField(max_length=100)
|
|
last_name = models.CharField(max_length=100)
|
|
primary_phone = models.CharField(max_length=20)
|
|
secondary_phone = models.CharField(max_length=20, blank=True, null=True)
|
|
email = models.EmailField()
|
|
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='team_member')
|
|
|
|
class Meta:
|
|
ordering = ['last_name', 'first_name']
|
|
verbose_name_plural = "Profiles"
|
|
|
|
def __str__(self):
|
|
return f"{self.first_name} {self.last_name}"
|
|
|
|
|
|
class Customer(models.Model):
|
|
"""Customer model with contact information"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
name = models.CharField(max_length=200)
|
|
primary_contact_first_name = models.CharField(max_length=100)
|
|
primary_contact_last_name = models.CharField(max_length=100)
|
|
primary_contact_phone = models.CharField(max_length=20)
|
|
primary_contact_email = models.EmailField()
|
|
secondary_contact_first_name = models.CharField(max_length=100, blank=True, null=True)
|
|
secondary_contact_last_name = models.CharField(max_length=100, blank=True, null=True)
|
|
secondary_contact_phone = models.CharField(max_length=20, blank=True, null=True)
|
|
secondary_contact_email = models.EmailField(blank=True, null=True)
|
|
billing_contact_first_name = models.CharField(max_length=100)
|
|
billing_contact_last_name = models.CharField(max_length=100)
|
|
billing_street_address = models.CharField(max_length=255)
|
|
billing_city = models.CharField(max_length=100)
|
|
billing_state = models.CharField(max_length=100)
|
|
billing_zip_code = models.CharField(max_length=20)
|
|
billing_email = models.EmailField()
|
|
billing_terms = models.TextField()
|
|
start_date = models.DateField()
|
|
end_date = models.DateField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['name']
|
|
verbose_name_plural = "Customers"
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Account(models.Model):
|
|
"""Account model belonging to a customer"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='accounts')
|
|
name = models.CharField(max_length=200)
|
|
street_address = models.CharField(max_length=255)
|
|
city = models.CharField(max_length=100)
|
|
state = models.CharField(max_length=100)
|
|
zip_code = models.CharField(max_length=20)
|
|
contact_first_name = models.CharField(max_length=100)
|
|
contact_last_name = models.CharField(max_length=100)
|
|
contact_phone = models.CharField(max_length=20)
|
|
contact_email = models.EmailField()
|
|
start_date = models.DateField()
|
|
end_date = models.DateField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['name']
|
|
verbose_name_plural = "Accounts"
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.customer.name})"
|
|
|
|
|
|
class Revenue(models.Model):
|
|
"""Revenue records for accounts"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='revenues')
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
start_date = models.DateField()
|
|
end_date = models.DateField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-start_date']
|
|
verbose_name_plural = "Revenues"
|
|
|
|
def __str__(self):
|
|
return f"{self.account.name} - ${self.amount}"
|
|
|
|
|
|
class Labor(models.Model):
|
|
"""Labor records for accounts"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='labors')
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
start_date = models.DateField()
|
|
end_date = models.DateField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-start_date']
|
|
verbose_name_plural = "Labors"
|
|
|
|
def __str__(self):
|
|
return f"{self.account.name} - ${self.amount}"
|
|
|
|
|
|
class Schedule(models.Model):
|
|
"""Service schedules for accounts"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='schedules')
|
|
monday_service = models.BooleanField(default=False)
|
|
tuesday_service = models.BooleanField(default=False)
|
|
wednesday_service = models.BooleanField(default=False)
|
|
thursday_service = models.BooleanField(default=False)
|
|
friday_service = models.BooleanField(default=False)
|
|
saturday_service = models.BooleanField(default=False)
|
|
sunday_service = models.BooleanField(default=False)
|
|
weekend_service = models.BooleanField(default=False)
|
|
schedule_exception = models.TextField(blank=True, null=True)
|
|
start_date = models.DateField()
|
|
end_date = models.DateField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-start_date']
|
|
verbose_name_plural = "Schedules"
|
|
|
|
def __str__(self):
|
|
return f"Schedule for {self.account.name}"
|
|
|
|
|
|
class Service(models.Model):
|
|
"""Service records for accounts"""
|
|
STATUS_CHOICES = (
|
|
('scheduled', 'Scheduled'),
|
|
('in_progress', 'In Progress'),
|
|
('completed', 'Completed'),
|
|
('cancelled', 'Cancelled'),
|
|
)
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='services')
|
|
date = models.DateField()
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='scheduled')
|
|
team_members = models.ManyToManyField(Profile, related_name='services')
|
|
notes = models.TextField(blank=True, null=True)
|
|
deadline_start = models.DateTimeField()
|
|
deadline_end = models.DateTimeField()
|
|
|
|
class Meta:
|
|
ordering = ['-date']
|
|
verbose_name_plural = "Services"
|
|
|
|
def __str__(self):
|
|
return f"Service for {self.account.name} on {self.date}"
|
|
|
|
|
|
class Project(models.Model):
|
|
"""Project records for customers"""
|
|
STATUS_CHOICES = (
|
|
('planned', 'Planned'),
|
|
('in_progress', 'In Progress'),
|
|
('completed', 'Completed'),
|
|
('cancelled', 'Cancelled'),
|
|
)
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')
|
|
account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name='projects', blank=True, null=True)
|
|
date = models.DateField()
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='planned')
|
|
team_members = models.ManyToManyField(Profile, related_name='projects')
|
|
notes = models.TextField(blank=True, null=True)
|
|
labor = models.DecimalField(max_digits=10, decimal_places=2)
|
|
amount = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
|
|
|
|
class Meta:
|
|
ordering = ['-date']
|
|
verbose_name_plural = "Projects"
|
|
|
|
def __str__(self):
|
|
return f"Project for {self.customer.name} on {self.date}"
|
|
|
|
|
|
class Invoice(models.Model):
|
|
"""Invoice records"""
|
|
STATUS_CHOICES = (
|
|
('draft', 'Draft'),
|
|
('sent', 'Sent'),
|
|
('paid', 'Paid'),
|
|
('overdue', 'Overdue'),
|
|
('cancelled', 'Cancelled'),
|
|
)
|
|
PAYMENT_CHOICES = (
|
|
('check', 'Check'),
|
|
('credit_card', 'Credit Card'),
|
|
('bank_transfer', 'Bank Transfer'),
|
|
('cash', 'Cash'),
|
|
)
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
date = models.DateField()
|
|
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='invoices')
|
|
accounts = models.ManyToManyField(Account, related_name='invoices', blank=True)
|
|
projects = models.ManyToManyField(Project, related_name='invoices', blank=True)
|
|
revenues = models.ManyToManyField(Revenue, related_name='invoices', blank=True)
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
|
|
date_paid = models.DateField(blank=True, null=True)
|
|
payment_type = models.CharField(max_length=20, choices=PAYMENT_CHOICES, blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-date']
|
|
verbose_name_plural = "Invoices"
|
|
|
|
def __str__(self):
|
|
return f"Invoice for {self.customer.name} on {self.date}"
|
|
|
|
|
|
class Report(models.Model):
|
|
"""Report records"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
date = models.DateField()
|
|
team_member = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name='reports')
|
|
services = models.ManyToManyField(Service, related_name='reports', blank=True)
|
|
projects = models.ManyToManyField(Project, related_name='reports', blank=True)
|
|
|
|
class Meta:
|
|
ordering = ['-date']
|
|
verbose_name_plural = "Reports"
|
|
|
|
def __str__(self):
|
|
return f"Report by {self.team_member.first_name} {self.team_member.last_name} on {self.date}"
|
|
|
|
|
|
class Punchlist(models.Model):
|
|
"""
|
|
Punchlist records for projects.
|
|
This is a customizable checklist for service projects with sections for
|
|
different areas and equipment. Modify the fields to match your specific
|
|
service requirements.
|
|
"""
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
project = models.ForeignKey('Project', on_delete=models.CASCADE, related_name='punchlists')
|
|
account = models.ForeignKey('Account', on_delete=models.CASCADE, related_name='punchlists')
|
|
date = models.DateField()
|
|
second_visit = models.BooleanField(default=False)
|
|
second_date = models.DateTimeField(blank=True, null=True)
|
|
|
|
# Front area section
|
|
front_ceiling = models.BooleanField(default=False)
|
|
front_vents = models.BooleanField(default=False)
|
|
front_fixtures = models.BooleanField(default=False)
|
|
front_counter = models.BooleanField(default=False)
|
|
|
|
# Main work area section
|
|
main_equipment = models.CharField(max_length=20, blank=True)
|
|
main_equipment_disassemble = models.BooleanField(default=False)
|
|
main_equipment_reassemble = models.BooleanField(default=False)
|
|
main_equipment_test = models.BooleanField(default=False)
|
|
main_equipment_exterior = models.BooleanField(default=False)
|
|
main_walls = models.BooleanField(default=False)
|
|
main_fixtures = models.BooleanField(default=False)
|
|
main_ceiling = models.BooleanField(default=False)
|
|
main_vents = models.BooleanField(default=False)
|
|
main_floors = models.BooleanField(default=False)
|
|
|
|
# Equipment section
|
|
equip_station_1 = models.BooleanField(default=False)
|
|
equip_station_2 = models.BooleanField(default=False)
|
|
equip_station_3 = models.BooleanField(default=False)
|
|
equip_station_4 = models.BooleanField(default=False)
|
|
equip_station_5 = models.BooleanField(default=False)
|
|
equip_station_6 = models.BooleanField(default=False)
|
|
equip_station_7 = models.BooleanField(default=False)
|
|
equip_sinks = models.BooleanField(default=False)
|
|
equip_dispensers = models.BooleanField(default=False)
|
|
equip_other = models.BooleanField(default=False)
|
|
|
|
# Back area section
|
|
back_ceiling = models.BooleanField(default=False)
|
|
back_vents = models.BooleanField(default=False)
|
|
|
|
# End of visit section
|
|
end_trash = models.BooleanField(default=False)
|
|
end_clean = models.BooleanField(default=False)
|
|
end_secure = models.BooleanField(default=False)
|
|
|
|
# Notes
|
|
notes = models.TextField(blank=True)
|
|
|
|
# Timestamps
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
sheet_url = models.URLField(blank=True, null=True)
|
|
pdf_url = models.URLField(blank=True, null=True)
|
|
exported_at = models.DateTimeField(blank=True, null=True)
|
|
|
|
class Meta:
|
|
ordering = ['-date']
|
|
verbose_name_plural = "Punchlists"
|
|
|
|
def __str__(self):
|
|
return f"Punchlist for {self.account.name} on {self.date}"
|