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 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 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, effort_type="REGULATION"): 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"), ("REPORT", "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 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}"