From 8b0cd13159b0c905bea571e68c58d399f146117a Mon Sep 17 00:00:00 2001 From: FreneticScribbler Date: Sat, 15 Oct 2022 19:09:51 +0100 Subject: [PATCH] Initially create subhire model and form --- RIGS/forms.py | 15 +++ RIGS/models.py | 165 +++++++++++++++------------- RIGS/templates/base_rigs.html | 2 + RIGS/templates/subhire_form.html | 180 +++++++++++++++++++++++++++++++ RIGS/urls.py | 3 + RIGS/views/rigboard.py | 14 +++ 6 files changed, 307 insertions(+), 72 deletions(-) create mode 100644 RIGS/templates/subhire_form.html diff --git a/RIGS/forms.py b/RIGS/forms.py index 5a136de1..29ee554d 100644 --- a/RIGS/forms.py +++ b/RIGS/forms.py @@ -124,6 +124,21 @@ class EventForm(forms.ModelForm): 'purchase_order', 'collector'] +class SubhireForm(forms.ModelForm): + related_models = { + 'person': models.Person, + 'organisation': models.Organisation, + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['end_date'].widget.format = '%Y-%m-%d' + + class Meta: + model = models.Subhire + fields = '__all__' + + class BaseClientEventAuthorisationForm(forms.ModelForm): tos = forms.BooleanField(required=True, label="Terms of hire") name = forms.CharField(label="Your Name") diff --git a/RIGS/models.py b/RIGS/models.py index afb7c520..b1d290c6 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -304,8 +304,29 @@ class EventManager(models.Manager): return qs -@reversion.register(follow=['items']) -class Event(models.Model, RevisionMixin): +def find_earliest_event_time(event, datetime_list): + # If there is no start time defined, pretend it's midnight + startTimeFaked = False + if event.has_start_time: + startDateTime = datetime.datetime.combine(event.start_date, event.start_time) + else: + startDateTime = datetime.datetime.combine(event.start_date, datetime.time(00, 00)) + startTimeFaked = True + + # timezoneIssues - apply the default timezone to the naiive datetime + tz = pytz.timezone(settings.TIME_ZONE) + startDateTime = tz.localize(startDateTime) + datetime_list.append(startDateTime) # then add it to the list + + earliest = min(datetime_list).astimezone(tz) # find the earliest datetime in the list + + # if we faked it & it's the earliest, better own up + if startTimeFaked and earliest == startDateTime: + return event.start_date + return earliest + + +class BaseEvent(models.Model, RevisionMixin): # Done to make it much nicer on the database PROVISIONAL = 0 CONFIRMED = 1 @@ -321,31 +342,85 @@ class Event(models.Model, RevisionMixin): name = models.CharField(max_length=255) person = models.ForeignKey('Person', null=True, blank=True, on_delete=models.CASCADE) organisation = models.ForeignKey('Organisation', blank=True, null=True, on_delete=models.CASCADE) - venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE) description = models.TextField(blank=True, default='') - notes = models.TextField(blank=True, default='') status = models.IntegerField(choices=EVENT_STATUS_CHOICES, default=PROVISIONAL) - dry_hire = models.BooleanField(default=False) - is_rig = models.BooleanField(default=True) - based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, - null=True) # Timing start_date = models.DateField() start_time = models.TimeField(blank=True, null=True) end_date = models.DateField(blank=True, null=True) end_time = models.TimeField(blank=True, null=True) + + purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO') + + class Meta: + abstract = True + + @property + def cancelled(self): + return (self.status == self.CANCELLED) + + @property + def confirmed(self): + return (self.status == self.BOOKED or self.status == self.CONFIRMED) + + @property + def has_start_time(self): + return self.start_time is not None + + @property + def has_end_time(self): + return self.end_time is not None + + @property + def latest_time(self): + """Returns the end of the event - this function could return either a tzaware datetime, or a naiive date object""" + tz = pytz.timezone(settings.TIME_ZONE) + endDate = self.end_date + if endDate is None: + endDate = self.start_date + + if self.has_end_time: + endDateTime = datetime.datetime.combine(endDate, self.end_time) + tz = pytz.timezone(settings.TIME_ZONE) + endDateTime = tz.localize(endDateTime) + + return endDateTime + + else: + return endDate + + def clean(self): + errdict = {} + if self.end_date and self.start_date > self.end_date: + errdict['end_date'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.'] + + startEndSameDay = not self.end_date or self.end_date == self.start_date + hasStartAndEnd = self.has_start_time and self.has_end_time + if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time: + errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.'] + return errdict + + +@reversion.register(follow=['items']) +class Event(BaseEvent): + mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, + verbose_name="MIC", on_delete=models.CASCADE) + venue = models.ForeignKey('Venue', blank=True, null=True, on_delete=models.CASCADE) + notes = models.TextField(blank=True, default='') + dry_hire = models.BooleanField(default=False) + is_rig = models.BooleanField(default=True) + based_on = models.ForeignKey('Event', on_delete=models.SET_NULL, related_name='future_events', blank=True, + null=True) + access_at = models.DateTimeField(blank=True, null=True) meet_at = models.DateTimeField(blank=True, null=True) - # Crew management + # Dry-hire only checked_in_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_checked_in', blank=True, null=True, on_delete=models.CASCADE) - mic = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='event_mic', blank=True, null=True, - verbose_name="MIC", on_delete=models.CASCADE) # Monies - purchase_order = models.CharField(max_length=255, blank=True, default='', verbose_name='PO') collector = models.CharField(max_length=255, blank=True, default='', verbose_name='collected by') # Authorisation request details @@ -395,26 +470,10 @@ class Event(models.Model, RevisionMixin): def total(self): return Decimal(self.sum_total + self.vat).quantize(Decimal('.01')) - @property - def cancelled(self): - return (self.status == self.CANCELLED) - - @property - def confirmed(self): - return (self.status == self.BOOKED or self.status == self.CONFIRMED) - @property def hs_done(self): return self.riskassessment is not None and len(self.checklists.all()) > 0 - @property - def has_start_time(self): - return self.start_time is not None - - @property - def has_end_time(self): - return self.end_time is not None - @property def earliest_time(self): """Finds the earliest time defined in the event - this function could return either a tzaware datetime, or a naiive date object""" @@ -428,45 +487,10 @@ class Event(models.Model, RevisionMixin): if self.meet_at: datetime_list.append(self.meet_at) - # If there is no start time defined, pretend it's midnight - startTimeFaked = False - if self.has_start_time: - startDateTime = datetime.datetime.combine(self.start_date, self.start_time) - else: - startDateTime = datetime.datetime.combine(self.start_date, datetime.time(00, 00)) - startTimeFaked = True - - # timezoneIssues - apply the default timezone to the naiive datetime - tz = pytz.timezone(settings.TIME_ZONE) - startDateTime = tz.localize(startDateTime) - datetime_list.append(startDateTime) # then add it to the list - - earliest = min(datetime_list).astimezone(tz) # find the earliest datetime in the list - - # if we faked it & it's the earliest, better own up - if startTimeFaked and earliest == startDateTime: - return self.start_date + earliest = find_earliest_event_time(self, datetime_list) return earliest - @property - def latest_time(self): - """Returns the end of the event - this function could return either a tzaware datetime, or a naiive date object""" - tz = pytz.timezone(settings.TIME_ZONE) - endDate = self.end_date - if endDate is None: - endDate = self.start_date - - if self.has_end_time: - endDateTime = datetime.datetime.combine(endDate, self.end_time) - tz = pytz.timezone(settings.TIME_ZONE) - endDateTime = tz.localize(endDateTime) - - return endDateTime - - else: - return endDate - @property def internal(self): return bool(self.organisation and self.organisation.union_account) @@ -487,14 +511,7 @@ class Event(models.Model, RevisionMixin): return f"{self.display_id}: {self.name}" def clean(self): - errdict = {} - if self.end_date and self.start_date > self.end_date: - errdict['end_date'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.'] - - startEndSameDay = not self.end_date or self.end_date == self.start_date - hasStartAndEnd = self.has_start_time and self.has_end_time - if startEndSameDay and hasStartAndEnd and self.start_time > self.end_time: - errdict['end_time'] = ['Unless you\'ve invented time travel, the event can\'t finish before it has started.'] + errdict = super.clean() if self.access_at is not None: if self.access_at.date() > self.start_date: @@ -555,6 +572,10 @@ class EventAuthorisation(models.Model, RevisionMixin): return f"{self.event.display_id} (requested by {self.sent_by.initials})" +class Subhire(BaseEvent): + insurance_value = models.DecimalField(max_digits=10, decimal_places=2) # TODO Validate if this is over notifiable threshold + # TODO Associated events + class InvoiceManager(models.Manager): def outstanding_invoices(self): # Manual query is the only way I have found to do this efficiently. Not ideal but needs must diff --git a/RIGS/templates/base_rigs.html b/RIGS/templates/base_rigs.html index 0ae1a62f..59b071c8 100644 --- a/RIGS/templates/base_rigs.html +++ b/RIGS/templates/base_rigs.html @@ -30,6 +30,8 @@ {% if perms.RIGS.add_event %} New Event + + New Subhire {% endif %} diff --git a/RIGS/templates/subhire_form.html b/RIGS/templates/subhire_form.html new file mode 100644 index 00000000..0b1a5066 --- /dev/null +++ b/RIGS/templates/subhire_form.html @@ -0,0 +1,180 @@ +{% extends 'base_rigs.html' %} + +{% load widget_tweaks %} +{% load static %} +{% load multiply from filters %} +{% load button from filters %} + +{% block css %} + {{ block.super }} + + +{% endblock %} + +{% block preload_js %} + {{ block.super }} + + +{% endblock %} + +{% block js %} + {{ block.super }} + + + + + +{% endblock %} + +{% block content %} +
+ {% csrf_token %} +
+ {% include 'form_errors.html' %} +
+ {# Contact details #} +
+
+
Contact Details
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+ {# Event details #} +
+
+
Event Details
+
+
+ +
+ {% render_field form.name class+="form-control" %} +
+
+
+ +
+
+
+ {% render_field form.start_date class+="form-control" %} +
+
+ {% render_field form.start_time class+="form-control" step="60" %} +
+
+
+
+
+ +
+
+
+ {% render_field form.end_date class+="form-control" %} +
+
+ {% render_field form.end_time class+="form-control" step="60" %} +
+
+
+
+
+ +
+ {% render_field form.status class+="form-control" %} +
+
+
+ +
+ {% render_field form.purchase_order class+="form-control" %} +
+
+
+
+
+
+
Equipment Information
+
+
+ +
+ {% render_field form.description class+="form-control" %} +
+
+
+ +
+
£
+ {% render_field form.insurance_value class+="form-control" %} +
+
+ If this value is greater than £50,000 then please email productions@nottinghamtec.co.uk in addition to complete the additional insurance requirements +
+
+
+
+
+ {% button 'submit' %} +
+
+{% endblock %} diff --git a/RIGS/urls.py b/RIGS/urls.py index b67e3c93..d51efb50 100644 --- a/RIGS/urls.py +++ b/RIGS/urls.py @@ -70,6 +70,9 @@ urlpatterns = [ path('event//duplicate/', permission_required_with_403('RIGS.add_event')(views.EventDuplicate.as_view()), name='event_duplicate'), + path('subhire/create/', permission_required_with_403('RIGS.add_event')(views.SubhireCreate.as_view()), + name='subhire_create'), + # Event H&S path('event/hs/', permission_required_with_403('RIGS.view_riskassessment')(views.HSList.as_view()), name='hs_list'), diff --git a/RIGS/views/rigboard.py b/RIGS/views/rigboard.py index 1a146c47..20284d33 100644 --- a/RIGS/views/rigboard.py +++ b/RIGS/views/rigboard.py @@ -100,6 +100,20 @@ class EventCreate(generic.CreateView): return reverse_lazy('event_detail', kwargs={'pk': self.object.pk}) +class SubhireCreate(generic.CreateView): + model = models.Subhire + form_class = forms.SubhireForm + template_name = 'subhire_form.html' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['page_title'] = "New Subhire" + context['edit'] = True + form = context['form'] + get_related(form, context) + return context + + class EventUpdate(generic.UpdateView): model = models.Event form_class = forms.EventForm