from itertools import chain
from django.conf import settings
from django.db import models
from django.db.models import Q
from django.utils.text import slugify
from myuser.models import TeamUser
ES_YEAR_LENGTH = 3
class Common(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class RegulatoryRole(Common):
name = models.CharField(max_length=64, null=False, blank=False)
description = models.TextField(max_length=1024)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Regulatory Roles"
class Person(Common):
"External person, rather than MOD at this point."
first_name = models.CharField(max_length=64, null=False, blank=False)
last_name = models.CharField(max_length=64, null=False, blank=False)
organisation = models.ForeignKey("Organisation", null=False, blank=False, on_delete=models.CASCADE)
email = models.EmailField(null=True, blank=True)
mobile = models.CharField(max_length=64, null=True, blank=True)
regulatory_role = models.ManyToManyField(RegulatoryRole)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Meta:
verbose_name_plural = "People"
class RegulatedEntityType(Common):
name = models.CharField(max_length=128, null=False, blank=False)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Regulated Entity Types"
class Organisation(Common):
name = models.CharField(max_length=128, blank=False)
slug = models.SlugField(max_length=128, blank=True)
is_regulated_entity = models.BooleanField(default=False)
entitytype = models.ForeignKey(RegulatedEntityType, null=True, blank=True, on_delete=models.CASCADE)
# Responsible Person
rp = models.ManyToManyField(
Person,
blank=True,
related_name="responsible_person",
verbose_name="Responsible Person",
)
# Accountable Person
ap = models.ManyToManyField(
Person,
blank=True,
related_name="accountable_person",
verbose_name="Accountable Person",
)
# Information Holder
ih = models.ManyToManyField(
Person,
blank=True,
related_name="information_holder",
verbose_name="Information Holder",
)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def lead_team(self):
teams = {ot.team for ot in self.lead_inspector.all()}
if not teams:
return "NA"
if len(teams) > 1:
if len(set([t.name for t in teams])) > 1:
return "Shared Lead"
else:
return teams.pop()
else:
return teams.pop()
class Meta:
verbose_name_plural = "Organisations"
class EngagementType(Common):
name = models.CharField(max_length=56)
description = models.TextField(null=True, blank=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Engagement Types"
class EngagementManager(models.Manager):
def sp(self):
# TODO fix this - returns duplicates
qs = self.filter(officers__team__name="Submarines and Propulsion")
return qs.exclude(Q(engagement_type__name="INSPECTION") | Q(engagement_type__name="ASSESSMENT"))
def sp_regulatory(self):
"Assessments and Inspectons only"
qs = self.filter(officers__team__name="Submarines and Propulsion")
return qs.filter(
Q(engagement_type__name="INSPECTION")
| Q(engagement_type__name="ASSESSMENT")
| Q(engagement_type__name="L1RIF")
| Q(engagement_type__name="L2RIF")
| Q(engagement_type__name="L3RIF")
| Q(engagement_type__name="L4RIF")
| Q(engagement_type__name="SAMPLING")
)
def tr(self):
return self.filter(officers__team__name="Transport")
class Engagement(Common):
proposed_start_date = models.DateField(null=False, blank=False)
proposed_end_date = models.DateField(null=True, blank=True)
engagement_type = models.ForeignKey(EngagementType, on_delete=models.CASCADE)
external_party = models.ForeignKey(Organisation, on_delete=models.CASCADE)
officers = models.ManyToManyField(TeamUser)
objects = EngagementManager()
friendly_types = {
"INSPECTION": "Inspection",
"ASSESSMENT": "Assessment",
"SAMPLING": "Sampling",
"L1RIF": "L1 RIF",
"L2RIF": "L2 RIF",
"L3RIF": "L3 RIF",
"L4RIF": "L4 RIF",
}
def get_officers(self):
officers_from_effort = list(chain.from_iterable([x.officers.all() for x in self.effort.all()]))
return [o.fullname() for o in set(officers_from_effort + list(self.officers.all()))]
# return [" ".join([x.first_name, x.last_name]) for x in self.officers.all()]
def total_planning_effort(self):
p_effort = self.engagementeffort_set.all().filter(is_planned=True)
return sum([x.effort_total_hours() for x in p_effort])
def total_planning_effort_per_officer(self):
# TODO - check this algorithm. It's not quite right is it?
p_effort = self.engagementeffort_set.all().filter(is_planned=True)
offs = sum([x.officers.count() for x in p_effort])
return sum([x.effort_total_hours() for x in p_effort]) / offs
def total_effort(self):
return 0
def friendly_type(self):
return self.friendly_types[self.engagement_type.name]
def dscs(self):
"Return all declared DSCs as part of REGULATION effort"
dscs = set()
for ee in EngagementEffort.objects.filter(engagement=self):
for si in ee.sub_instruments.all():
dscs.add(si)
return dscs
def __str__(self):
return f"{self.engagement_type.name} at {self.external_party} ({self.proposed_start_date})"
class Meta:
ordering = ("proposed_start_date",)
class EngagementEffort(Common):
choices = (
("TRAVEL", "Travel"),
("PLANNING", "Planning"),
("REGULATION", "Regulation (On-site or Remote)"),
("DISCUSSION", "Discussion"),
("REPORTING", "Reporting"),
)
is_planned = models.BooleanField(null=True, blank=True, verbose_name="Planned", default=True)
effort_type = models.CharField(max_length=32, choices=choices, verbose_name="Effort Type")
proposed_start_date = models.DateTimeField()
proposed_end_date = models.DateTimeField()
engagement = models.ForeignKey(Engagement, on_delete=models.CASCADE, related_name="effort")
officers = models.ManyToManyField(settings.AUTH_USER_MODEL)
sub_instruments = models.ManyToManyField("instruments.SubInstrument", blank=True, related_name="effort")
notes = models.TextField(null=True, blank=True)
class Meta:
verbose_name_plural = "Engagement Effort"
ordering = ("proposed_start_date",)
def get_officers(self):
return [" ".join([x.first_name, x.last_name]) for x in self.officers.all()]
def effort_total_hours(self):
"Returns total effort for this engagement."
delta = self.proposed_end_date - self.proposed_start_date
return delta.seconds / 60 / 60
def effort_total_planned_hours(self):
"Returns total planned effort for this engagement."
if self.is_planned:
delta = self.proposed_end_date - self.proposed_start_date
return delta.seconds / 60 / 60
else:
return 0
def effort_actual(self):
"Returns effort that where is_planned is false."
if not self.is_planned:
delta = self.proposed_end_date - self.proposed_start_date
return delta.seconds / 60 / 60
else:
return 0
def effort_per_officer_hours(self):
"Returns effort in hours per officer"
delta = (self.proposed_end_date - self.proposed_start_date) / self.officers.count()
return delta.seconds / 60 / 60
def __str__(self):
return f"{self.effort_type} effort for {self.engagement}: {self.proposed_end_date - self.proposed_start_date}"
class RegulatoryCycle(Common):
start_date = models.DateField(null=False, blank=False)
end_date = models.DateField(null=False, blank=False)
description = models.TextField(max_length=1024, null=True, blank=True)
def get_year(self):
return str(self.start_date.year)
def __str__(self):
return f"Regulatory Cycle: {self.get_year()}"
class EngagementStrategy(Common):
STATUS = (
("DRAFT", "Draft"),
("SUBMITTED", "Submitted"),
("APPROVED", "Approved"),
("REJECTED", "Rejected"),
)
organisation = models.ForeignKey(Organisation, on_delete=models.CASCADE)
start_year = models.ForeignKey(RegulatoryCycle, on_delete=models.CASCADE, related_name="start_year")
end_year = models.ForeignKey(RegulatoryCycle, on_delete=models.CASCADE, related_name="end_year")
description = models.TextField(max_length=1024, null=True, blank=True)
inspector_sign_off = models.DateField(null=True, blank=True)
owned_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="owned_engagement_strategies"
)
reviewed_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.CASCADE,
related_name="reviewed_engagement_strategies",
)
management_sign_off = models.DateField(null=True, blank=True)
status = models.CharField(max_length=32, choices=STATUS, default=STATUS[0][0])
def get_end_year(self) -> int:
return self.start_year.start_date.year + (ES_YEAR_LENGTH - 1)
def __str__(self):
return (
f"Engagement Strategy ({self.start_year.start_date.year}-{self.get_end_year()}) - {self.organisation.name}"
)
class Meta:
ordering = ("start_year",)
unique_together = ("start_year", "organisation")
verbose_name_plural = "Engagement Strategies"