summaryrefslogtreecommitdiffstats
path: root/engagements/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'engagements/models.py')
-rw-r--r--engagements/models.py236
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}"