summaryrefslogblamecommitdiffstats
path: root/engagements/models.py
blob: 24acabd432e24ef7609047cb0b4092827706e70f (plain) (tree)
1
2
3
4
5
6
7
8
9

                           






                                     

                  





















                                                                   
 






























































































































                                                                                                        
                           




                                                                                                       
 


















                                                                          
                                                                   
















                                                                                                   
                                   













                                                                                                            


                                                                                   



























                                                                                                                      













                                                                          







                                   


                                                                                                        
                                                                          
                                                                









                                                                                                      

                                                                                  












                                                                                                                       
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 "toss"

        # 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"