diff options
author | Matthew Lemon <y@yulqen.org> | 2024-04-23 11:16:38 +0100 |
---|---|---|
committer | Matthew Lemon <y@yulqen.org> | 2024-04-23 11:16:38 +0100 |
commit | 0f951dcf029d4af284467543a3afdf5bf6581a20 (patch) | |
tree | a48384210cdc168e3bd3ccff6d6d516eeed9e748 /engagements/models.py | |
parent | 8b084e9fe7a5f3a04c32daf9a24f7f2cf67300f9 (diff) |
switched to Django
Diffstat (limited to '')
-rw-r--r-- | engagements/models.py | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/engagements/models.py b/engagements/models.py new file mode 100644 index 0000000..20bed8d --- /dev/null +++ b/engagements/models.py @@ -0,0 +1,236 @@ +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}" |