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 | |
parent | 8b084e9fe7a5f3a04c32daf9a24f7f2cf67300f9 (diff) |
switched to Django
Diffstat (limited to 'engagements')
27 files changed, 2043 insertions, 0 deletions
diff --git a/engagements/__init__.py b/engagements/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/engagements/__init__.py diff --git a/engagements/admin.py b/engagements/admin.py new file mode 100644 index 0000000..9861867 --- /dev/null +++ b/engagements/admin.py @@ -0,0 +1,32 @@ +from django.contrib import admin + +from .models import ( + Engagement, + EngagementEffort, + EngagementType, + Organisation, + Person, + RegulatedEntityType, + RegulatoryRole, +) + +site = admin.site + +site.site_header = "DefNucSyR Engagement Database (DED)" + + +class PersonAdmin(admin.ModelAdmin): + list_display = ("__str__", "organisation") + + @admin.display(description="fullname") + def fullname(self, obj): + return f"{obj.first_name} {obj.last_name}" + + +site.register(Person, PersonAdmin) +site.register(Organisation) +site.register(RegulatedEntityType) +site.register(RegulatoryRole) +site.register(EngagementType) +site.register(Engagement) +site.register(EngagementEffort) diff --git a/engagements/apps.py b/engagements/apps.py new file mode 100644 index 0000000..e2f8f69 --- /dev/null +++ b/engagements/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EngagementsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "engagements" diff --git a/engagements/forms.py b/engagements/forms.py new file mode 100644 index 0000000..ae01916 --- /dev/null +++ b/engagements/forms.py @@ -0,0 +1,242 @@ +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Field, Fieldset, Layout, Submit +from django import forms +from django.forms.widgets import HiddenInput + +from .models import Engagement, EngagementEffort + +# TODO - need to handle errors correctly in this form and in the template + + +class EngagementEffortReportingCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "officers", + "notes", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = [ + "is_planned", + "proposed_start_date", + "proposed_end_date", + "officers", + "notes", + ] + help_texts = { + "is_planned": ("<br><small><em>To distinguish planned events from retrospective recording</em></small>") + } + widgets = { + "proposed_start_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + } + + +class EngagementEffortRegulationCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "sub_instruments", + "officers", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = [ + "is_planned", + "proposed_start_date", + "proposed_end_date", + "officers", + "sub_instruments", + "notes", + ] + help_texts = { + "is_planned": ("<br><small><em>To distinguish planned events from retrospective recording</em></small>") + } + widgets = { + "proposed_start_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + } + + +class EngagementEffortPlanningCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "officers", + "notes", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = [ + "is_planned", + "proposed_start_date", + "proposed_end_date", + "officers", + "notes", + ] + help_texts = { + "is_planned": ("<br><small><em>To distinguish planned events from retrospective recording</em></small>") + } + widgets = { + "proposed_start_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={ + "class": "w3-input w3-border w3-round", + "type": "datetime-local", + }, + format="j M y H:i", + ), + } + + +class EngagementEffortTravelCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = FormHelper() + self.helper.layout = Layout( + Fieldset( + "This is a form", + Field("is_planned"), + Field("proposed_start_date", css_class="w3-input w3-border w3-round", type="date"), + Field("proposed_end_date", css_class="w3-input w3-border w3-round"), + "officers", + ), + Submit("submit", "Submit", css_class="w3-button w3-green"), + ) + + class Meta: + model = EngagementEffort + fields = ["is_planned", "proposed_start_date", "proposed_end_date", "officers"] + widgets = { + "proposed_start_date": forms.DateTimeInput(attrs={"type": "datetime-local"}), + "proposed_end_date": forms.DateTimeInput(attrs={"type": "datetime-local"}), + } + + +class EngagementEffortCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["engagement"].widget = HiddenInput() + self.fields["effort_type"].widget = HiddenInput() + if kwargs.get("initial"): + if not kwargs["initial"]["effort_type"] == "REGULATION": + self.fields["sub_instruments"].widget = HiddenInput() + + class Meta: + model = EngagementEffort + fields = "__all__" + widgets = { + "is_planned": forms.Select( + choices=( + (True, "YES"), + (False, "NO"), + ), + attrs={"class": "w3-border"}, + ), + "proposed_start_date": forms.DateTimeInput( + attrs={"class": "w3-input w3-border w3-round", "type": "date"}, + format="j M y H:i", + ), + "proposed_end_date": forms.DateTimeInput( + attrs={"class": "w3-input w3-border w3-round", "type": "date"}, + format="j M y H:i", + ), + "effort_type": forms.Select(attrs={"class": "w3-select w3-border w3-round"}), + "officers": forms.SelectMultiple(attrs={"class": "w3-select w3-border w3-round"}), + "sub_instruments": forms.SelectMultiple(attrs={"class": "w3-select w3-border w3-round"}), + } + help_texts = { + "proposed_start_date": "<small><em>YYYY-MM-DD HH:MM</em> " + "(<strong>Please include time here</strong>)</small>", + "proposed_end_date": "<small><em>YYYY-MM-DD HH:MM</em> " + "(<strong>Please include time here</strong>)</small>", + } + + +class EngagementCreateForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + try: + self.fields["engagement_type"].queryset = kwargs["initial"]["engagement_type"] + except KeyError: + pass + + class Meta: + model = Engagement + fields = [ + "proposed_start_date", + "proposed_end_date", + "engagement_type", + "officers", + ] + labels = { + "officers": "Inspectors", + } + help_texts = { + "proposed_start_date": "<small><em>YYYY-MM-DD</em></small>", + "proposed_end_date": "<small><em>YYYY-MM-DD</em></small>", + } + widgets = { + "proposed_start_date": forms.DateInput(attrs={"class": "w3-input w3-border w3-round", "type": "date"}), + "proposed_end_date": forms.DateInput(attrs={"class": "w3-input w3-border w3-round", "type": "date"}), + "engagement_type": forms.Select(attrs={"class": "w3-select w3-input w3-border w3-round"}), + "officers": forms.SelectMultiple(attrs={"class": "w3-input w3-border w3-round"}), + } diff --git a/engagements/management/commands/create_engagement_data.py b/engagements/management/commands/create_engagement_data.py new file mode 100644 index 0000000..021d29b --- /dev/null +++ b/engagements/management/commands/create_engagement_data.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand + +from engagements.utils import populate_database + + +class Command(BaseCommand): + help = "python manage.py create_engagement_data" + + # def add_arguments(self, parser): + # parser.add_argument("year", nargs="+", type=int) + + def handle(self, *args, **options): + populate_database() + self.stdout.write(self.style.SUCCESS("Created engagement objects.")) diff --git a/engagements/migrations/0001_initial.py b/engagements/migrations/0001_initial.py new file mode 100644 index 0000000..2516517 --- /dev/null +++ b/engagements/migrations/0001_initial.py @@ -0,0 +1,134 @@ +# Generated by Django 4.0.8 on 2022-11-02 09:00 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Engagement', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('proposed_start_date', models.DateField()), + ('proposed_end_date', models.DateField(blank=True, null=True)), + ], + options={ + 'ordering': ('proposed_start_date',), + }, + ), + migrations.CreateModel( + name='EngagementType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=56)), + ('description', models.TextField(blank=True, null=True)), + ], + options={ + 'verbose_name_plural': 'Engagement Types', + }, + ), + migrations.CreateModel( + name='Organisation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=128)), + ('slug', models.SlugField(blank=True, max_length=128)), + ('is_regulated_entity', models.BooleanField(default=False)), + ], + options={ + 'verbose_name_plural': 'Organisations', + }, + ), + migrations.CreateModel( + name='RegulatedEntityType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=128)), + ], + options={ + 'verbose_name_plural': 'Regulated Entity Types', + }, + ), + migrations.CreateModel( + name='RegulatoryRole', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=64)), + ('description', models.TextField(max_length=1024)), + ], + options={ + 'verbose_name_plural': 'Regulatory Roles', + }, + ), + migrations.CreateModel( + name='Person', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('first_name', models.CharField(max_length=64)), + ('last_name', models.CharField(max_length=64)), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('mobile', models.CharField(blank=True, max_length=64, null=True)), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.organisation')), + ('regulatory_role', models.ManyToManyField(to='engagements.regulatoryrole')), + ], + options={ + 'verbose_name_plural': 'People', + }, + ), + migrations.AddField( + model_name='organisation', + name='ap', + field=models.ManyToManyField(blank=True, related_name='accountable_person', to='engagements.person', verbose_name='Accountable Person'), + ), + migrations.AddField( + model_name='organisation', + name='entitytype', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='engagements.regulatedentitytype'), + ), + migrations.AddField( + model_name='organisation', + name='ih', + field=models.ManyToManyField(blank=True, related_name='information_holder', to='engagements.person', verbose_name='Information Holder'), + ), + migrations.AddField( + model_name='organisation', + name='rp', + field=models.ManyToManyField(blank=True, related_name='responsible_person', to='engagements.person', verbose_name='Responsible Person'), + ), + migrations.CreateModel( + name='EngagementEffort', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('is_planned', models.BooleanField(blank=True, default=True, null=True, verbose_name='Planned')), + ('effort_type', models.CharField(choices=[('TRAVEL', 'Travel'), ('PLANNING', 'Planning'), ('REGULATION', 'Regulation (On-site or Remote)'), ('DISCUSSION', 'Discussion'), ('REPORT', 'Reporting')], max_length=32, verbose_name='Effort Type')), + ('proposed_start_date', models.DateTimeField()), + ('proposed_end_date', models.DateTimeField()), + ('engagement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='effort', to='engagements.engagement')), + ], + options={ + 'verbose_name_plural': 'Engagement Effort', + 'ordering': ('proposed_start_date',), + }, + ), + ] diff --git a/engagements/migrations/0002_initial.py b/engagements/migrations/0002_initial.py new file mode 100644 index 0000000..1a55d23 --- /dev/null +++ b/engagements/migrations/0002_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 4.0.8 on 2022-11-02 09:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('instruments', '0001_initial'), + ('engagements', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='engagementeffort', + name='officers', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='engagementeffort', + name='sub_instruments', + field=models.ManyToManyField(blank=True, related_name='effort', to='instruments.subinstrument'), + ), + migrations.AddField( + model_name='engagement', + name='engagement_type', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.engagementtype'), + ), + migrations.AddField( + model_name='engagement', + name='external_party', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='engagements.organisation'), + ), + migrations.AddField( + model_name='engagement', + name='officers', + field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/engagements/migrations/0003_engagementeffort_notes.py b/engagements/migrations/0003_engagementeffort_notes.py new file mode 100644 index 0000000..c3071c2 --- /dev/null +++ b/engagements/migrations/0003_engagementeffort_notes.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.8 on 2023-04-19 17:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("engagements", "0002_initial"), + ] + + operations = [ + migrations.AddField( + model_name="engagementeffort", + name="notes", + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/engagements/migrations/__init__.py b/engagements/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/engagements/migrations/__init__.py 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}" diff --git a/engagements/static/js/yoap.js b/engagements/static/js/yoap.js new file mode 100644 index 0000000..84d5a2f --- /dev/null +++ b/engagements/static/js/yoap.js @@ -0,0 +1,12 @@ +// bllopcks +const heading = document.createElement("h3"); +const headingText = document.createTextNode("Snatch grab"); +heading.appendChild(headingText); + +const mydiv = document.body.querySelector("#test-container") +mydiv.appendChild(heading); + +var i; +for (i = 0; i < 15; i++) { + +} diff --git a/engagements/templates/engagements/engagement_create.html b/engagements/templates/engagements/engagement_create.html new file mode 100644 index 0000000..1e72ca0 --- /dev/null +++ b/engagements/templates/engagements/engagement_create.html @@ -0,0 +1,29 @@ +{% extends "core/base.html" %} + +{% block title %}Create new engagement{% endblock title %} + +{% block content %} + +<div class="w3-container w3-cell-row w3-margin-bottom"> + <h2>{{ title }}</h2> + <div class="w3-panel w3-light-blue w3-round w3-leftbar w3-border-blue w3-display-container"> + + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h4>Step 1</h4> + <p>To roughly plan out future events, you provide the minimal details here: <strong>start date</strong>, <strong>end date</strong> (optional),the <strong>type of Engagement</strong> (Assessment, Inspection or Sampling), the <strong>external site</strong> or operation and finally the <strong>inspectors</strong> who are carrying out the work.</p> + <h4>Step 2</h4> + <p>So that we can track the finer details involved with an Assessment or Inspection, each Engagement comprises additional <em>components</em>, such as <strong>Planning</strong>, <strong>On-site</strong> and <strong>Reporting</strong>. Inspector time can be allocated to these components. In addition, each compontent can be associated with <strong>Instruments</strong>, such as <strong>DSCs</strong>, etc. After you create the overarching Engagement using this form, you will have the opportunity to add components.</p> + </div> + + <hr> + <div class="w3-container w3-cell-middle"> + <h4>Enter main details:</h4> + <form method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" class="w3-btn w3-green" value="Save"> + </form> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/engagement_detail.html b/engagements/templates/engagements/engagement_detail.html new file mode 100644 index 0000000..85ef220 --- /dev/null +++ b/engagements/templates/engagements/engagement_detail.html @@ -0,0 +1,78 @@ +{% extends "core/base.html" %} +{% load table_extras %} + +{% block title %}{{ engagement }}{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <div class="w3-container w3-center"> + <h2>{{ engagement.friendly_type }} at {{ engagement.external_party }}</h2> + </div> + <div class="w3-row"> + <div class="w3-container"> + <div> + <a href="{% url 'engagements:edit' engagement.pk %}">Edit Engagement</a> + </div> + <section class="w3-container w3-blue"> + <h3>Details</h3> + </section> + <table class="w3-table w3-border w3-light-gray w3-bordered"> + <tr> + <td><strong>Date</strong></td> + <td>{{ engagement.proposed_start_date|date:'l' }} - {{ engagement.proposed_start_date|date:'j M Y' }}</td> + </tr> + <tr> + <td><strong>Site/Operation:</strong></td> + <td>{{ engagement.external_party }} </td> + </tr> + <tr> + <td><strong>Subject of Activity</strong></td> + <td> + <p>Summmary text</p> + <ul class="w3-ul"> + {% for t in dscs %} + <li><a href='#'>{{ t }}</a></li> + {% endfor %} + </ul> + </td> + </tr> + <tr> + <td><strong>Inspectors:</strong></td> + <td>{{ engagement.officers.all|commalist }}</td> + </tr> + <tr> + <td><strong>Planned Effort:</strong></td> + <td>{{ effort_planned|floatformat }} hrs</td> + </tr> + <tr> + <td><strong>Actual Effort:</strong></td> + <td>{{ effort_actual|floatformat }} hrs</td> + </tr> + <tr> + <td><strong>Total Effort:</strong></td> + <td>{{ effort_total|floatformat }} hrs</td> + </tr> + </table> + <hr> + </div> + + <div class="w3-container w3-light-gray"> + <h3>Effort for this engagement</h3> + Add: + <a href="{% url 'engagements:effort_create' engagement.pk "TRAVEL" %}">Travel</a> | + <a href="{% url 'engagements:effort_create' engagement.pk "PLANNING" %}">Planning</a> | + <a href="{% url 'engagements:effort_create' engagement.pk "REGULATION" %}">Regulation</a> | + <a href="{% url 'engagements:effort_create' engagement.pk "REPORTING" %}">Reporting</a> + + {% if effort %} + {% for e in effort.all %} + <div id="planned_swap_{{ e.id }}"> + {% include "engagements/snippets/effort_summary_panel.html" with e=e %} + </div> + {% endfor %} + {% endif %} + </div> + </div> + + {% endblock content %} diff --git a/engagements/templates/engagements/engagement_effort_create.html b/engagements/templates/engagements/engagement_effort_create.html new file mode 100644 index 0000000..65e2e21 --- /dev/null +++ b/engagements/templates/engagements/engagement_effort_create.html @@ -0,0 +1,20 @@ +{% extends "core/base.html" %} + +{% load crispy_forms_tags %} + +{% block title %}Add {{ etype }} effort to Engagement{% endblock title %} + +{% block content %} + + <div class="w3-container w3-cell-row w3-margin-bottom"> + <h2>Register your {{ etype|lower }} effort for the {{ engagement.engagement_type.name|title }} event at {{ engagement.external_party }} on {{ engagement.proposed_start_date }}</h2> + + <div class="w3-container w3-cell-middle"> + <h4>Enter details:</h4> + <form method="post">{% csrf_token %} + {% crispy form form.helper %} + </form> + </div> + </div> + +{% endblock content %} diff --git a/engagements/templates/engagements/engagement_form.html b/engagements/templates/engagements/engagement_form.html new file mode 100644 index 0000000..e05c639 --- /dev/null +++ b/engagements/templates/engagements/engagement_form.html @@ -0,0 +1,32 @@ +{% extends "core/base.html" %} + +{% load static %} + +{% block title %}Create new engagement{% endblock title %} +{% block extra_head_tags %} +<link rel="stylesheet" href="{% static 'css/styles.css' %}"/> +{% endblock extra_head_tags %} + +{% block content %} + +<div class="format-container"> + <div class="form-thing"> + <h2>{{ title }}</h2> + <p>TAKE THIS OUT</p> + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h4>Step 1</h4> + <p>To roughly plan out future events, you provide the minimal details here: <strong>start date</strong>, <strong>end date</strong> (optional),the <strong>type of Engagement</strong> (Assessment, Inspection or Sampling), the <strong>external site</strong> or operation and finally the <strong>inspectors</strong> who are carrying out the work.</p> + <h4>Step 2</h4> + <p>So that we can track the finer details involved with an Assessment or Inspection, each Engagement comprises additional <em>components</em>, such as <strong>Planning</strong>, <strong>On-site</strong> and <strong>Reporting</strong>. Inspector time can be allocated to these components. In addition, each compontent can be associated with <strong>Instruments</strong>, such as <strong>DSCs</strong>, etc. After you create the overarching Engagement using this form, you will have the opportunity to add components.</p> + </div> + <div class="form-thing"> + <h4>Enter main details:</h4> + <form method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" class="w3-btn w3-green" value="Save"> + </form> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/engagement_plan_for_site.html b/engagements/templates/engagements/engagement_plan_for_site.html new file mode 100644 index 0000000..55ab54b --- /dev/null +++ b/engagements/templates/engagements/engagement_plan_for_site.html @@ -0,0 +1,35 @@ +{% extends "core/base.html" %} + +{% block title %}Create new engagement{% endblock title %} + +{% block content %} + +<div class="w3-container w3-cell-row w3-margin-bottom"> + <h2>{{ title }}</h2> + <div class="w3-panel w3-light-blue w3-round w3-leftbar w3-border-blue w3-display-container"> + + <span onclick="this.parentElement.style.display='none'" + class="w3-button w3-display-topright">×</span> + <h4>Step 1</h4> + <p>To roughly plan out future events, you provide the minimal details here: <strong>start date</strong>, <strong>end date</strong> (optional),the <strong>type of Engagement</strong> (Assessment, Inspection or Sampling), the <strong>external site</strong> or operation and finally the <strong>inspectors</strong> who are carrying out the work.</p> + <h4>Step 2</h4> + <p>So that we can track the finer details involved with an Assessment or Inspection, each Engagement comprises additional <em>components</em>, such as <strong>Planning</strong>, <strong>On-site</strong> and <strong>Reporting</strong>. Inspector time can be allocated to these components. In addition, each compontent can be associated with <strong>Instruments</strong>, such as <strong>DSCs</strong>, etc. After you create the overarching Engagement using this form, you will have the opportunity to add components.</p> + </div> + + <hr> + <div class="w3-cell-row"> + <div class="w3-container w3-cell"> + <h4>Enter main details:</h4> + <form method="post">{% csrf_token %} + {{ form.as_p }} + <input type="submit" class="w3-btn w3-green" value="Save"> + </form> + </div> + <div class="w3-container w3-cell"> + <h4>Current Engagement Plan for X</h4> + </div> + + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/ep_org.html b/engagements/templates/engagements/ep_org.html new file mode 100644 index 0000000..202100a --- /dev/null +++ b/engagements/templates/engagements/ep_org.html @@ -0,0 +1,427 @@ +{% extends "core/base.html" %} + +{% block title %}Create new engagement{% endblock title %} + +{% load table_extras %} + +{% load static %} + +{% block content %} + +<div class="w3-container"> + <h2>Engagement Plans for {{ entity.name }}</h2> + <a href="{% url 'engagements:create' entity.slug 'reg' %}">Add New Regulatory Engagement</a> | <a href="{% url 'engagements:create' entity.slug %}">Add New Engagement</a> +</div> + +<div class="w3-container"> + <h3>2023</h3> + <table class="ep-table"> + <thead> + <tr> + <th>Start Date</th> + <th>End Date</th> + <th>Engagement</th> + <th>Engagement Type</th> + <th>Area/Theme</th> + <th>Inspectors Involved</th> + <th>DSCs</th> + <th>Travel Effort</th> + <th>Planning and Regulation Effort</th> + <th>Completion Status</th> + </tr> + </thead> + <tbody> + {% for e in engagements %} + <tr> + <td>{{ e.proposed_start_date|date:'j M Y' }}</td> + <td>{{ e.proposed_end_date|date:'j M Y' }}</td> + <td><a href="{% url 'engagements:engagement_detail' e.pk %}">{{ e }}</a></td> + <td>{{ e.friendly_type }}</td> + <td>Area Theme</td> + <td> + <ul class="w3-ul"> + {% for inspector in e.officers.all %} + <li>{{ inspector }} </li> + {% endfor %} + </ul> + </td> + <td> + <ul class="w3-ul"> + {% for dsc in e.dscs %} + <li><a href="#">{{ dsc.short }}</a></li> + {% endfor %} + </ul> + </td> + <td>TBC</td> + <td>TBC</td> + <td class="ep-table">Incomplete</td> + </tr> + {% endfor %} + </tbody> + </table> + +</div> + +<hr> + +<div class="w3-container"> + <div class="w3-cell-row"> + <div class="w3-container w3-cell"> + <h3>Total DSC coverage</h3> + <table class="summary-table"> + <tr> + <th>DSC</th> + <th>Allocated time</th> + </tr> + {% for dsc in dscs %} + <tr> + <td>{{ dsc }} </td> + <td>{{ dsc | effort_for_org:entity }}</td> + </tr> + {% endfor %} + </table> + </div> + <div class="w3-container w3-cell"> + <h3>Another table of data</h3> + <table class="summary-table"> + <tr> + <th>DSC</th> + <th>Allocated time</th> + </tr> + {% for dsc in dscs %} + <tr> + <td>{{ dsc }} </td> + <td>{{ dsc | effort_for_org:entity }}</td> + </tr> + {% endfor %} + </table> + </div> + + </div> +</div> + +<style type="text/css" media="screen"> + + /* (A) CONTAINER */ + /* https://code-boxx.com/beginner-create-grid-html-css/ */ + #grid-col { + + /* (A1) GRID LAYOUT */ + display: grid; + + /* (A2) SPECIFY COLUMNS */ + grid-template-columns: 75px repeat(31, 40px); + + /* we can also specify exact pixels, percentage, repeat + grid-template-columns: 50px 100px 150px; + grid-template-columns: 25% 50% 25%; + grid-template-columns: 100px 20% auto; + grih-template-columns: repeat(3, auto); */ + } + + /* (B) GRID CELLS */ + div.cell { + background: white; + border: 1px solid lightgray; + padding: 10px; + } + + div.cellred{ + background: red; + border: 1px solid lightgray; + padding: 10px; + } + + div.cellyellow{ + background: yellow; + border: 1px solid lightgray; + padding: 10px; + } + div.cellorange{ + background: orange; + border: 1px solid lightgray; + padding: 10px; + } + /* (C) RESPONSIVE - 1 COLUMN ON SMALL SCREENS */ + /* @media screen and (max-width: 640px) { + #grid-col { grid-template-columns: 100%; } + } + */ +/* </style> +<!-- <div class="w3-container w3-padding-16"> --> +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell"></div><div class="cell">1</div> --> +<!-- <div class="cell">2</div><div class="cell">3</div> --> +<!-- <div class="cell">4</div><div class="cell">5</div> --> +<!-- <div class="cell">6</div><div class="cell">7</div> --> +<!-- <div class="cell">8</div><div class="cell">9</div> --> +<!-- <div class="cell">10</div><div class="cell">11</div> --> +<!-- <div class="cell">12</div><div class="cell">13</div> --> +<!-- <div class="cell">14</div><div class="cell">15</div> --> +<!-- <div class="cell">16</div><div class="cell">17</div> --> +<!-- <div class="cell">18</div><div class="cell">19</div> --> +<!-- <div class="cell">20</div><div class="cell">21</div> --> +<!-- <div class="cell">22</div><div class="cell">23</div> --> +<!-- <div class="cell">24</div><div class="cell">25</div> --> +<!-- <div class="cell">26</div><div class="cell">27</div> --> +<!-- <div class="cell">28</div><div class="cell">29</div> --> +<!-- <div class="cell">30</div><div class="cell">31</div> --> +<!-- </div> --> +<!-- </div> --> +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Jan</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cellred"><small></small></div><div class="cellred"><small></small></div> --> +<!-- <div class="cellred"><small></small></div><div class="cellred"><small></small></div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Feb</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cellyellow"><small></small></div><div class="cellyellow"><small></small></div> --> +<!-- <div class="cellyellow"><small></small></div><div class="cellyellow"><small></small></div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Mar</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cellorange"><small></small></div><div class="cellorange"><small></small></div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- <div class="cell">-</div><div class="cell">-</div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Apr</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">May</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Jun</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Jul</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Aug</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Sep</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Oct</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Nov</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + +<!-- <div class="grid"> --> +<!-- <div id="grid-col"> --> +<!-- <div class="cell">Dec</div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- <div class="cell"></div><div class="cell"></div> --> +<!-- </div> --> +<!-- </div> --> + + +<script src="{% static 'js/yoap.js' %}"></script> + +{% endblock content %} diff --git a/engagements/templates/engagements/index.html b/engagements/templates/engagements/index.html new file mode 100644 index 0000000..8d5df36 --- /dev/null +++ b/engagements/templates/engagements/index.html @@ -0,0 +1,30 @@ +{% extends "core/base.html" %} + +{% block title %}Engagements{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <h2>Engagement Planning</h2> + <div class="w3-container"> + + <h3>Your entities</h3> + + <table class="w3-table w3-bordered"> + <tr> + <th>Engagement Plan</th> + <th>Scheduled Engagements</th> + <th>Total time</th> + </tr> + {% for e in entities %} + <tr> + <td><a href="{% url 'engagements:plan_for_org' e.slug %}">{{ e.name }}</a></td> + <td>{{ e.engagement_set.all.count }}</td> + <td>NA</td> + </tr> + {% endfor %} + </table> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/organisations.html b/engagements/templates/engagements/organisations.html new file mode 100644 index 0000000..5bbd1b5 --- /dev/null +++ b/engagements/templates/engagements/organisations.html @@ -0,0 +1,50 @@ +{% extends "core/base.html" %} +{% load table_extras %} + +{% block title %}Regulated Entities{% endblock title %} + +{% block content %} + +<div class="w3-container"> + <h1>Regulated Entities</h1> + <div class="w3-container"> + <table class="w3-table w3-bordered w3-striped"> + <tr> + <th>Entity</th> + <th>Team</th> + <th>Lead Inspector[s]</th> + <th>Responsible Person[s]</th> + <th>Accountable Person[s]</th> + </tr> + {% for e in entities %} + <tr> + <td>{{ e.name }}</td> + <td>{{ e.lead_team }}</td> + <td> + {% if e.lead_inspector.all %} + {{ e.lead_inspector.all|commalist }} + {% endif %} + </td> + <td> + <!-- rp --> + {% if e.rp.all %} + {% for p in e.rp.all %} + {{ p }} + {% endfor %} + {% endif %} + </td> + <td> + <!-- ap --> + {% if e.ap.all %} + {% for p in e.ap.all %} + {{ p }} + {% endfor %} + {% endif %} + </td> + </tr> + {% endfor %} + </table> + </div> +</div> + +{% endblock content %} diff --git a/engagements/templates/engagements/snippets/effort_summary_panel.html b/engagements/templates/engagements/snippets/effort_summary_panel.html new file mode 100644 index 0000000..235a67a --- /dev/null +++ b/engagements/templates/engagements/snippets/effort_summary_panel.html @@ -0,0 +1,41 @@ +{% load table_extras %} + +{% if e.is_planned %} + <div class="w3-panel w3-pale-green w3-topbar w3-border-green"> +{% else %} + <div class="w3-panel w3-light-gray w3-topbar w3-border-green"> +{% endif %} + <div class="w3-cell-row"> + <div class="w3-cell w3-align-left"> + <h5>{{ e.proposed_start_date|date:'j M Y' }} - {{ e.effort_type }}</h5> + </div> + <div class="w3-cell w3-right-align"> + <p> {{ e.effort_total_hours|floatformat }} hrs</p> + Planned: {{ e.is_planned }} + <button hx-get="{% url 'engagements:htmx-effort-planned' e.id %}" hx-target="#planned_swap_{{ e.id }}"> + Flip + </button> + </div> + </div> + <div> + <strong>Inspectors:</strong> {{ e.officers.all|commalist }} + </div> + <div> + <strong>Start time</strong>: {{ e.proposed_start_date|date:'H:i' }} + {% if e.proposed_end_date %} + - {{ e.proposed_end_date|date:'H:i' }} + {% endif %} + </div> + + <div> + {% if e.sub_instruments.all %} + <strong>DSCs</strong>: + <ul> + {% for dsc in e.sub_instruments.all %} + <li><a href="#">{{ dsc.title }}</a></li> + {% endfor %} + </ul> + {% endif %} + + </div> +</div> diff --git a/engagements/tests/__init__.py b/engagements/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/engagements/tests/__init__.py diff --git a/engagements/tests/test_forms.py b/engagements/tests/test_forms.py new file mode 100644 index 0000000..b6aab9a --- /dev/null +++ b/engagements/tests/test_forms.py @@ -0,0 +1,45 @@ +from django.test import TestCase + +from engagements.forms import EngagementEffortCreateForm +from engagements.models import Engagement, EngagementType, Organisation +from myuser.models import TeamUser + + +class EngagementEffortCreate(TestCase): + def setUp(self): + data = { + "proposed_start_date": "2022-10-01", + "engagement_type": EngagementType.objects.create(name="ET1"), + "external_party": Organisation.objects.create(name="O1"), + } + self.e = Engagement.objects.create(**data) + self.user = TeamUser.objects.create_user(email="ming@ming.com") + + def test_basic_validation(self): + form = EngagementEffortCreateForm( + data={ + "is_planned": True, + "proposed_start_date": "2022-10-10 10:00", + "proposed_end_date": "2022-10-10 12:00", + "engagement": self.e, + "effort_type": "PLANNING", + "officers": [self.user], + } + ) + self.assertFalse(form.errors) + + def test_basic_validation_on_bad_entry(self): + form = EngagementEffortCreateForm( + data={ + "is_planned": True, + "proposed_start_date": "20240-10-10 10:00", + "proposed_end_date": "2022-10-10 12:00", + "engagement": self.e, + "effort_type": "bobbins", + "officers": [self.user], + "sub_instruments": [""], + } + ) + self.assertTrue(form.errors["effort_type"]) + self.assertTrue(form.errors["proposed_start_date"]) + self.assertTrue(form.errors["sub_instruments"]) diff --git a/engagements/tests/test_models.py b/engagements/tests/test_models.py new file mode 100644 index 0000000..08c5169 --- /dev/null +++ b/engagements/tests/test_models.py @@ -0,0 +1,30 @@ +import pytest +from django.test import TestCase + +from engagements.utils import populate_database + + +class TestModels(TestCase): + @classmethod + def setUpTestData(cls): + cls.data = populate_database() + + @pytest.mark.django_db + def test_check_all_dcs(self): + dscs = self.data.get("sub_instruments") + self.assertEqual(dscs[0].title, "DSC 1 - Title 1") + + @pytest.mark.django_db + def test_effort_by_type(self): + e = self.data["engagements"][0] + total_planning = sum([x.effort_total_planned_hours() for x in e.effort.filter(effort_type="PLANNING")]) + total_travel = sum([x.effort_total_planned_hours() for x in e.effort.filter(effort_type="TRAVEL")]) + total_regulation = sum([x.effort_total_planned_hours() for x in e.effort.filter(effort_type="REGULATION")]) + assert total_planning == 4.25 + assert total_regulation == 0 + assert total_travel == 1 + + # TODO finish this test! + def test_total_effort_for_engagement(self): + e = self.data["engagements"][0] + assert e.total_effort() == 5.25 diff --git a/engagements/tests/test_views.py b/engagements/tests/test_views.py new file mode 100644 index 0000000..f25eb3a --- /dev/null +++ b/engagements/tests/test_views.py @@ -0,0 +1,91 @@ +import datetime +from http import HTTPStatus + +from django.test import RequestFactory, TestCase +from django.urls import reverse + +from engagements import models, views +from engagements.utils import populate_database + + +class TestModels(TestCase): + @classmethod + def setUpTestData(cls): + cls.request = RequestFactory() # for use in _ep_request_factory test + cls.data = populate_database() + + def test_dscs_for_ep(self): + org = self.data["orgs"][0] + # we set up an engagement and effort for this org + et = models.EngagementType.objects.get(name="INSPECTION") + si = self.data["sub_instruments"][0] + si2 = self.data["sub_instruments"][2] + si3 = self.data["sub_instruments"][3] + # si_not = self.data["sub_instruments"][1] + engagement = models.Engagement.objects.create( + proposed_start_date=datetime.date(2022, 10, 10), + proposed_end_date=datetime.date(2022, 10, 10), + engagement_type=et, + external_party=org, + ) + ef1 = models.EngagementEffort.objects.create( + is_planned=True, + effort_type="REGULATION", + proposed_start_date=datetime.date(2022, 10, 10), + proposed_end_date=datetime.date(2022, 10, 10), + engagement=engagement, + ) + ef1.sub_instruments.add(si) # DSC 1 + ef1.sub_instruments.add(si2) # DSC 3 + ef1.sub_instruments.add(si3) # DSC 4 + ef1.save() + url = reverse("engagements:plan_for_org", kwargs={"orgslug": org.slug}) + self.client.force_login(self.data["superuser"]) + response = self.client.get(url) + self.assertEqual(response.status_code, HTTPStatus.OK) + self.assertTrue(response.context["entity"]) + self.assertEqual(response.context["entity"].name, org.name) + self.assertIn(si, response.context["dscs"]) + self.assertIn(si2, response.context["dscs"]) + self.assertIn(si3, response.context["dscs"]) + self.assertEqual(response.context["dscs"].count(), 3) + # self.assertNotIn(si_not, response.context["dscs"]) + + def test_dscs_for_ep_request_factory(self): + """ + On the EP page, we expect to see a list of all DSCs related to effort + for this organisation. + + Included this here for reference + """ + org = self.data["orgs"][0] + url = reverse("engagements:plan_for_org", kwargs={"orgslug": org.slug}) + request = self.request.get(url) + request.user = self.data["superuser"] + response = views.engagement_plan_for(request, org.slug) + self.assertEqual(response.status_code, HTTPStatus.OK) + + +class TestEngagementEffortView(TestCase): + @classmethod + def setUpTestData(cls): + cls.request = RequestFactory() # for use in _ep_request_factory test + cls.data = populate_database() + + def test_get_blank_form(self): + url = reverse("engagements:effort_create", kwargs={"eid": 1, "etype": "PLANNING"}) + self.client.force_login(self.data["superuser"]) + request = self.request.get(url) + request.user = self.data["superuser"] + response = views.engagement_effort_create(request, eid=1, etype="PLANNING") + self.assertEqual(response.status_code, HTTPStatus.OK) + + # def test_post_data(self): + # url = reverse( + # "engagements:effort_create", kwargs={"eid": 1, "etype": "PLANNING"} + # ) + # self.client.force_login(self.data["superuser"]) + # request = self.request.post(url, {"proposed_start_date": "toss"}) + # request.user = self.data["superuser"] + # response = views.engagement_effort_create(request, eid=1, etype="PLANNING") + # self.assertEqual(response.status_code, HTTPStatus.OK) diff --git a/engagements/urls.py b/engagements/urls.py new file mode 100644 index 0000000..7b6ddd3 --- /dev/null +++ b/engagements/urls.py @@ -0,0 +1,29 @@ +from django.urls import path + +from . import views + +app_name = "engagements" +urlpatterns = [ + path("", views.engagement_planning, name="home"), + path("<int:pk>", views.engagement_detail, name="engagement_detail"), + path("plan/<slug:orgslug>/", views.engagement_plan_for, name="plan_for_org"), + path( + "regulatedentities/", + views.RegulatedEntitiesView.as_view(), + name="regulatedentities", + ), + path("edit/<int:eid>", views.engagement_edit, name="edit"), + path("create/<slug:slug>/", views.engagement_create, name="create"), + path("create/<slug:slug>/<str:reg>", views.engagement_create, name="create"), + path( + "effort/create/<int:eid>/<str:etype>", + views.engagement_effort_create, + name="effort_create", + ), +] + +htmx_urls = [ + path("htmx-effort-planned/<int:effid>", views.htmx_effort_planned, name="htmx-effort-planned") +] + +urlpatterns = urlpatterns + htmx_urls diff --git a/engagements/utils.py b/engagements/utils.py new file mode 100644 index 0000000..2c4ff72 --- /dev/null +++ b/engagements/utils.py @@ -0,0 +1,186 @@ +import random +from collections import defaultdict +from datetime import date, datetime + +from faker import Faker + +from engagements.models import ( + Engagement, + EngagementEffort, + EngagementType, + Organisation, + Person, + RegulatedEntityType, + RegulatoryRole, +) +from instruments.models import Instrument, SubInstrument +from myuser.models import Team, TeamUser + + +def populate_database(): + out = defaultdict(list) + fake = Faker(locale="en_GB") + + # Users and teams + TeamUser.objects.all().delete() + Team.objects.all().delete() + teams = ["Submarines and Propulsion", "Transport"] + Team.objects.all().delete() + tdefnuc = Team.objects.create(name=teams[0]) + u1 = TeamUser.objects.create_superuser( + email="lemon@lemon.com", + password="lemonlemon", + ) + u1.first_name = "Matthew" + u1.last_name = "Lemon" + u1.team = tdefnuc + u1.designation = "LI8" + u1.is_active = True + u1.save() + out["superuser"] = u1 + + desigs = ["LI1", "LI2", "LI3", "LI4", "LI5", "LI6"] + for p in range(6): + first_name = fake.first_name() + last_name = fake.last_name() + u = TeamUser.objects.create_superuser( + email=f"{first_name.lower()}@theregulator.com", + password="fakepassword", + ) + u.first_name = first_name + u.last_name = last_name + u.team = Team.objects.create(name=random.choice(teams)) + u.designation = desigs[p] + out["users"].append(u) + u.save() + + # RegulatoryRoles + RegulatoryRole.objects.all().delete() + RegulatoryRole.objects.create( + name="Responsible Person", + description="The Regulated Person charged with managing, etc", + ) + RegulatoryRole.objects.create(name="Accountable Person") + RegulatoryRole.objects.create( + name="Information Holder", + description="A regulated person who must ensure etc.", + ) + # RegulatedEntityTypes + RegulatedEntityType.objects.all().delete() + ret1 = RegulatedEntityType.objects.create(name="Site") + RegulatedEntityType.objects.create(name="Operation") + RegulatedEntityType.objects.create(name="Distrubuted Site") + # Organisations + Organisation.objects.all().delete() + for _ in range(10): + o = Organisation.objects.create(name=fake.company(), is_regulated_entity=True, entitytype=ret1) + if random.choice([1, 2, 3]) == 2: + u1.lead_for.add(o) + u1.save() + out["orgs"].append(o) + o1 = Organisation.objects.create(name="MOD", is_regulated_entity=False) + out["orgs"].append(o1) + # Instruments + Instrument.objects.all().delete() + j = Instrument.objects.create( + name="JSP 628", + long_title="Security Regulation of the DNE", + designator="JSP628", + owner=o1, + ) + # Create the DSCs + Faker.seed(0) + SubInstrument.objects.all().delete() + for n in range(1, 17): + si = SubInstrument.objects.create( + title=f"DSC {str(n)} - Title {n}", + itype="DSC", + parent=j, + short=f"DSC {n}", + description=fake.paragraph(nb_sentences=2), + rationale=fake.paragraph(nb_sentences=3), + ) + out["sub_instruments"].append(si) + for d in SubInstrument.objects.filter(itype="DSC"): + i = SubInstrument.objects.create( + title=f"DSTAIG {d.pk} - Title {d.pk}", + is_guidance=True, + itype="DSTAIG", + parent=j, + description=fake.paragraph(nb_sentences=2), + rationale=fake.paragraph(nb_sentences=3), + ) + out["sub_instruments"].append(i) + i.relative.add(d) + + # EngagementType + EngagementType.objects.all().delete() + EngagementType.objects.create(name="EMAIL", description=fake.paragraph(4)) + EngagementType.objects.create(name="MEETING", description=fake.paragraph(4)) + EngagementType.objects.create(name="BRIEFING", description=fake.paragraph(4)) + EngagementType.objects.create(name="TEAMSCALL", description=fake.paragraph(4)) + EngagementType.objects.create(name="PHONECALL", description=fake.paragraph(4)) + EngagementType.objects.create(name="L1RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="L2RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="L3RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="L4RIF", description=fake.paragraph(4)) + EngagementType.objects.create(name="SAMPLING", description=fake.paragraph(4)) + EngagementType.objects.create(name="INSPECTION", description=fake.paragraph(4)) + EngagementType.objects.create(name="ASSESSMENT", description=fake.paragraph(4)) + + # People + Person.objects.all().delete() + o_pks = [o.pk for o in Organisation.objects.all()] + for _ in range(5): + p = Person( + first_name=fake.first_name(), + last_name=fake.last_name(), + organisation=Organisation.objects.get(pk=random.choice(o_pks)), + mobile=fake.cellphone_number(), + ) + p.email = f"{p.first_name.lower()}@{p.organisation.slug}.com" + p.save() + out["people"].append(p) + + # Engagement + Engagement.objects.all().delete() + d1 = date(2022, 5, 10) + d2 = date(2022, 5, 12) + # users = get_user_model() + et = EngagementType.objects.get(name="INSPECTION") + ep = Organisation.objects.first() + e = Engagement.objects.create( + proposed_start_date=d1, + proposed_end_date=d2, + engagement_type=et, + external_party=ep, + ) + e.officers.add(u1) + e.save() + out["engagements"].append(e) + + # Effort + EngagementEffort.objects.all().delete() + d1 = datetime(2022, 4, 10, 10, 0, 0) + d2 = datetime(2022, 4, 10, 14, 15, 0) + d3 = datetime(2022, 4, 10, 12, 0, 0) + d4 = datetime(2022, 4, 10, 13, 0, 0) # 1 hour between d3 and d4 + ef = EngagementEffort.objects.create( + is_planned=True, + effort_type="PLANNING", + proposed_start_date=d1, + proposed_end_date=d2, + engagement=e, + ) + EngagementEffort.objects.create( + is_planned=True, + effort_type="TRAVEL", + proposed_start_date=d3, + proposed_end_date=d4, + engagement=e, + ) + ef.officers.add(u1) + ef.sub_instruments.add(out["sub_instruments"][0]) + ef.save() + out["engagement_effort"].append(ef) + return out diff --git a/engagements/views.py b/engagements/views.py new file mode 100644 index 0000000..fb804df --- /dev/null +++ b/engagements/views.py @@ -0,0 +1,182 @@ +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin +from django.db.models import Q +from django.shortcuts import get_object_or_404, redirect, render +from django.views.generic import ListView + +from instruments.models import SubInstrument + +from .forms import ( + EngagementCreateForm, + EngagementEffortPlanningCreateForm, + EngagementEffortRegulationCreateForm, + EngagementEffortReportingCreateForm, + EngagementEffortTravelCreateForm, +) +from .models import Engagement, EngagementEffort, EngagementType, Organisation + + +class RegulatedEntitiesView(ListView, LoginRequiredMixin): + context_object_name = "entities" + queryset = Organisation.objects.filter(is_regulated_entity=True).order_by("name") + template_name = "engagements/organisations.html" + + +@login_required +def engagement_planning(request): + user = request.user + reg_orgs = Organisation.objects.filter(is_regulated_entity=True) + my_orgs = reg_orgs.filter(lead_inspector=user) + return render(request, "engagements/index.html", {"entities": my_orgs}) + # display the list and ask which one? + + +@login_required +def htmx_effort_planned(request, effid): + if request.method == "GET": + effort = EngagementEffort.objects.get(id=effid) + if effort.is_planned is True: + effort.is_planned = False + else: + effort.is_planned = True + effort.save() + return render(request, "engagements/snippets/effort_summary_panel.html", {"e" : effort}) + + +@login_required +def engagement_detail(request, pk): + engagement = Engagement.objects.get(pk=pk) + subinstruments = SubInstrument.objects.filter(title__icontains="DSC") + effort = EngagementEffort.objects.filter(engagement=engagement).order_by("proposed_start_date") + dscs = [] + for e in effort: + subs = e.sub_instruments.all() + for s in subs: + dscs.append(s) + dscs = set(dscs) + effort_total = sum(e.effort_total_hours() for e in effort) + effort_planned = sum(e.effort_total_planned_hours() for e in effort) + effort_actual = sum(e.effort_actual() for e in effort) + context = { + "engagement": engagement, + "subinstruments": subinstruments, + "effort": effort, + "effort_total": effort_total, + "effort_planned": effort_planned, + "effort_actual": effort_actual, + "dscs": dscs, + } + return render(request, "engagements/engagement_detail.html", context) + + +@login_required +def engagement_plan_for(request, orgslug): + org = Organisation.objects.get(slug=orgslug) + engagements = Engagement.objects.filter(external_party=org) + dscs = SubInstrument.objects.filter(itype="DSC").filter(effort__engagement__external_party=org).distinct() + context = {"entity": org, "engagements": engagements, "dscs": dscs} + return render(request, "engagements/ep_org.html", context) + + +# class EngagementView(ListView, LoginRequiredMixin): +# context_object_name = "engagements" +# queryset = Engagement.objects.filter(engagement_type__name="INSPECTION") +# template_name = "engagements/index.html" + + +@login_required +def engagement_effort_create(request, eid, etype=None): + forms = { + "TRAVEL": EngagementEffortTravelCreateForm, + "PLANNING": EngagementEffortPlanningCreateForm, + "REGULATION": EngagementEffortRegulationCreateForm, + "REPORTING": EngagementEffortReportingCreateForm, + } + + if request.method == "POST": + engagement = Engagement.objects.get(pk=eid) + # use the specialised type of form on POST - it has the correct fields + form = forms[etype](request.POST) + if form.is_valid(): + eff = form.save(commit=False) + eff.engagement = engagement + eff.save() + eff.officers.add(request.user) + eff.effort_type = etype + eff.save() + form.save_m2m() + return redirect("engagements:engagement_detail", pk=eid) + else: + engagement = Engagement.objects.get(pk=eid) + form = forms.get(etype, EngagementCreateForm)( + initial={ + "proposed_start_date": engagement.proposed_start_date.isoformat(), + "engagement": engagement, + "effort_type": etype, + "officers": request.user, + }, + ) + return render( + request, + "engagements/engagement_effort_create.html", + {"form": form, "engagement": engagement, "etype": etype}, + ) + + +@login_required +def engagement_edit(request, eid): + e = get_object_or_404(Engagement, pk=eid) + form = EngagementCreateForm(request.POST or None, instance=e) + if request.method == "POST": + if form.is_valid(): + form.save() + return redirect("engagements:engagement_detail", pk=e.pk) + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": f"Edit Engagement {e}"}, + ) + + +@login_required +def engagement_create(request, slug, reg=None): + if request.method == "POST": + form = EngagementCreateForm(request.POST) + if form.is_valid(): + ef = form.save(commit=False) + ef.external_party = Organisation.objects.get(slug=slug) + ef.save() + return redirect("engagements:plan_for_org", orgslug=slug) + else: + if reg: + form = EngagementCreateForm( + initial={ + "engagement_type": EngagementType.objects.filter( + Q(name="ASSESSMENT") + | Q(name="INSPECTION") + | Q(name="SAMPLING") + | Q(name="L1RIF") + | Q(name="L2RIF") + | Q(name="L3RIF") + | Q(name="L1RIF") + ), + "external_party": Organisation.objects.get(slug=slug), + "officers": request.user, + } + ) + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": "Add New Regulatory Engagement"}, + ) + else: + form = EngagementCreateForm( + initial={ + "officers": request.user, + } + ) + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": "Add New Engagement"}, + ) |