Compare commits

...

11 Commits

18 changed files with 139 additions and 71 deletions

View File

@@ -82,7 +82,7 @@ class EventRiskAssessmentList(generic.ListView):
template_name = 'hs_object_list.html'
def get_queryset(self):
return self.model.objects.order_by('reviewed_at').select_related('event')
return self.model.objects.exclude(event__status=models.Event.CANCELLED).order_by('reviewed_at').select_related('event')
def get_context_data(self, **kwargs):
context = super(EventRiskAssessmentList, self).get_context_data(**kwargs)
@@ -187,6 +187,9 @@ class EventChecklistList(generic.ListView):
model = models.EventChecklist
template_name = 'hs_object_list.html'
def get_queryset(self):
return self.model.objects.exclude(event__status=models.Event.CANCELLED).order_by('reviewed_at').select_related('event')
def get_context_data(self, **kwargs):
context = super(EventChecklistList, self).get_context_data(**kwargs)
context['title'] = 'Event Checklist'
@@ -215,7 +218,7 @@ class HSList(generic.ListView):
template_name = 'hs_list.html'
def get_queryset(self):
return models.Event.objects.all().order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
return models.Event.objects.all().exclude(status=models.Event.CANCELLED).order_by('-start_date').select_related('riskassessment').prefetch_related('checklists')
def get_context_data(self, **kwargs):
context = super(HSList, self).get_context_data(**kwargs)

View File

@@ -370,6 +370,9 @@ class Event(models.Model, RevisionMixin):
@property
def vat(self):
# No VAT is owed on internal transfers
if self.internal:
return 0
return Decimal(self.sum_total * self.vat_rate.rate).quantize(Decimal('.01'))
"""

View File

@@ -8,7 +8,7 @@
<div class="row">
<div class="col-12 text-right my-3">
{% button 'edit' url='ec_edit' pk=object.pk %}
{% button 'view' url='event_detail' pk=object.pk text="Event" %}
{% button 'view' url='event_detail' pk=object.event.pk text="Event" %}
{% include 'partials/review_status.html' with perm=perms.RIGS.review_eventchecklist review='ec_review' %}
</div>
</div>

View File

@@ -190,19 +190,21 @@
{% endif %}
</para>
</td>
<td>£ {{ item.cost|floatformat:2 }}</td>
<td>£{{ item.cost|floatformat:2 }}</td>
<td>{{ item.quantity }}</td>
<td>£ {{ item.total_cost|floatformat:2 }}</td>
<td>£{{ item.total_cost|floatformat:2 }}</td>
</tr>
{% endfor %}
</blockTable>
<keepTogether>
<blockTable style="totalTable" colWidths="300,115,80">
{% if object.vat > 0 %}
<tr>
<td>{% if quote %}VAT Registration Number: 170734807{% endif %}</td>
<td>Total (ex. VAT)</td>
<td>{% if quote %}VAT Registration Number: 170734807</td>
<td>Total (ex. VAT){% endif %}</td>
<td>£ {{ object.sum_total|floatformat:2 }}</td>
</tr>
{% endif %}
<tr>
<td>
{% if quote %}
@@ -211,8 +213,10 @@
</para>
{% endif %}
</td>
{% if object.vat > 0 %}
<td>VAT @ {{ object.vat_rate.as_percent|floatformat:2 }}%</td>
<td>£ {{ object.vat|floatformat:2 }}</td>
<td>£{{ object.vat|floatformat:2 }}</td>
{% endif %}
</tr>
<tr>
<td>
@@ -224,7 +228,7 @@
</td>
{% if invoice %}
<td>Total</td>
<td>£ {{ object.total|floatformat:2 }}</td>
<td>£{{ object.total|floatformat:2 }}</td>
{% else %}
<td>
<para>
@@ -233,7 +237,7 @@
</td>
<td>
<para>
<b>£ {{ object.total|floatformat:2 }}</b>
<b>£{{ object.total|floatformat:2 }}</b>
</para>
</td>
{% endif %}
@@ -267,7 +271,7 @@
<tr>
<td>{{ payment.get_method_display }}</td>
<td>{{ payment.date }}</td>
<td>£ {{ payment.amount|floatformat:2 }}</td>
<td>£{{ payment.amount|floatformat:2 }}</td>
</tr>
{% endfor %}
</blockTable>
@@ -275,18 +279,18 @@
<tr>
<td></td>
<td>Payment Total</td>
<td>£ {{ object.invoice.payment_total|floatformat:2 }}</td>
<td>£{{ object.invoice.payment_total|floatformat:2 }}</td>
</tr>
<tr>
<td></td>
<td>
<para>
<b>Balance</b> (ex. VAT)
<b>Balance</b> {% if object.vat > 0 %}(ex. VAT){% endif %}
</para>
</td>
<td>
<para>
<b>£ {{ object.invoice.balance|floatformat:2 }}</b>
<b>£{{ object.invoice.balance|floatformat:2 }}</b>
</para>
</td>
</tr>
@@ -316,7 +320,7 @@
<tr>
<td>General Enquires and 24 Hour Emergency Contact: 0115 84 68720</td>
</tr>
{% else %}
{% elif object.vat > 0 %}
<tr>
<td>
<para>VAT Registration Number: 170734807</para>

View File

@@ -4,10 +4,11 @@
{% block content %}
<div class="table-responsive">
<table class="table mb-0">
<table class="table mb-0 table-sm">
<thead>
<tr>
<th scope="col">Event</th>
<th scope="col">MIC</th>
<th scope="col">Dates</th>
<th scope="col">RA</th>
<th scope="col">Checklists</th>
@@ -16,7 +17,8 @@
<tbody>
{% for event in object_list %}
<tr id="event_row">
<th scope="row" id="event_number"><a href="{% url 'event_detail' event.pk %}">{{ event }}</a></th>
<th scope="row" id="event_number"><a href="{% url 'event_detail' event.pk %}">{{ event }}</a><br><small>{{ event.get_status_display }}</small></th>
<td>{% if event.mic is not None %}<a href="{% url 'profile_detail' event.mic.pk %}">{% else %}<span class="text-danger">{% endif %}{{ event.mic }}{% if event.mic is not None %}</a>{% else %}</span>{%endif%}</td>
<!--Dates-->
<td id="event_dates">
<span><strong>{{ event.start_date|date:"D d/m/Y" }}</strong></span>

View File

@@ -15,7 +15,7 @@
<div class="row">
<div class="col-12">
<div class="table-responsive">
<table class="table mb-0">
<table class="table mb-0 table-sm">
<thead>
<tr>
<th scope="col">Event</th>
@@ -32,7 +32,7 @@
{% for object in object_list %}
<tr class="{% if object.reviewed_by %}table-success{%endif%}">
{# General #}
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a></th>
<th scope="row"><a href="{% url 'event_detail' object.event.pk %}">{{ object.event }}</a><br><small>{{ object.event.get_status_display }}</small></th>
{% for field in object_list.0.fieldz %}
<td>{{ object|get_field:field }}</td>
{% endfor %}

View File

@@ -6,11 +6,11 @@
</div>
</th>
{% if perms.RIGS.view_event %}
<td>£&nbsp;<span class="cost">{{item.cost|floatformat:2}}</span></td>
<td>£<span class="cost">{{item.cost|floatformat:2}}</span></td>
{% endif %}
<td class="quantity">{{item.quantity}}</td>
{% if perms.RIGS.view_event %}
<td>£&nbsp;<span class="sub-total" data-subtotal="{{item.total_cost}}">{{item.total_cost|floatformat:2}}</span></td>
<td>£<span class="sub-total" data-subtotal="{{item.total_cost}}">{{item.total_cost|floatformat:2}}</span></td>
{% endif %}
{% if edit %}
<td class="vert-align text-right">

View File

@@ -27,12 +27,13 @@
{% endfor %}
</tbody>
{% if auth or perms.RIGS.view_event %}
<tfoot>
<tfoot style="font-weight: bold">
<tr>
<td rowspan="3" colspan="2"></td>
<td>Total (ex. VAT)</td>
<td colspan="2">£ <span id="sumtotal">{{object.sum_total|default:0|floatformat:2}}</span></td>
<td>Total {% if object.vat > 0 or not object.pk %}(ex. VAT){% endif %}</td>
<td colspan="2">£<span id="sumtotal">{{object.sum_total|default:0|floatformat:2}}</span></td>
</tr>
{% if object.vat > 0 or not object.pk %}
<tr>
{% if not object.pk %}
<td id="vat-rate" data-rate="{{currentVAT.rate}}">VAT @
@@ -41,12 +42,13 @@
<td id="vat-rate" data-rate="{{object.vat_rate.rate}}">VAT @
{{object.vat_rate.as_percent|floatformat|default:"TBD"}}%</td>
{% endif %}
<td colspan="2">£ <span id="vat">{{object.vat|default:0|floatformat:2}}</span></td>
<td colspan="2">£<span id="vat">{{object.vat|default:0|floatformat:2}}</span></td>
</tr>
<tr>
<td>Total</td>
<td colspan="2">£ <span id="total">{{object.total|default:0|floatformat:2}}</span></td>
<td colspan="2">£<span id="total">{{object.total|default:0|floatformat:2}}</span></td>
</tr>
{% endif %}
</tfoot>
{% endif %}
</table>
@@ -59,9 +61,9 @@
<em class="description"></em>
</div>
</td>
<td>£&nbsp;<span class="cost"></span></td>
<td>£<span class="cost"></span></td>
<td class="quantity"></td>
<td>£&nbsp;<span class="sub-total"></span></td>
<td>£<span class="sub-total"></span></td>
{% if edit %}
<td class="vert-align text-right">
<div class="btn-group" role="group" aria-label="Action buttons">

8
assets/converters.py Normal file
View File

@@ -0,0 +1,8 @@
class AssetIDConverter: # Forces lowercase to uppercase
regex = '[^/]+'
def to_python(self, value):
return str(value).upper()
def to_url(self, value):
return str(value).upper()

View File

@@ -1,22 +1,24 @@
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.urls import path, register_converter
from django.views.decorators.clickjacking import xframe_options_exempt
from PyRIGS.decorators import has_oembed, permission_required_with_403
from PyRIGS.views import OEmbedView
from assets import views
from . import views, converters
register_converter(converters.AssetIDConverter, 'asset')
urlpatterns = [
path('', login_required(views.AssetList.as_view()), name='asset_index'),
path('asset/list/', login_required(views.AssetList.as_view()), name='asset_list'),
path('asset/id/<str:pk>/', has_oembed(oembed_view="asset_oembed")(views.AssetDetail.as_view()), name='asset_detail'),
path('asset/id/<asset:pk>/', has_oembed(oembed_view="asset_oembed")(views.AssetDetail.as_view()), name='asset_detail'),
path('asset/create/', permission_required_with_403('assets.add_asset')
(views.AssetCreate.as_view()), name='asset_create'),
path('asset/id/<str:pk>/edit/', permission_required_with_403('assets.change_asset')
path('asset/id/<asset:pk>/edit/', permission_required_with_403('assets.change_asset')
(views.AssetEdit.as_view()), name='asset_update'),
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.add_asset')
path('asset/id/<asset:pk>/duplicate/', permission_required_with_403('assets.add_asset')
(views.AssetDuplicate.as_view()), name='asset_duplicate'),
path('asset/id/<str:pk>/label', login_required(views.GenerateLabel.as_view()), name='generate_label'),
path('asset/id/<asset:pk>/label', login_required(views.GenerateLabel.as_view()), name='generate_label'),
path('cabletype/list/', login_required(views.CableTypeList.as_view()), name='cable_type_list'),
path('cabletype/create/', permission_required_with_403('assets.add_cable_type')(views.CableTypeCreate.as_view()), name='cable_type_create'),

View File

@@ -48,9 +48,9 @@ class AssetList(LoginRequiredMixin, generic.ListView):
queryset = self.model.objects.all()
elif len(query_string) >= 3:
queryset = self.model.objects.filter(
Q(asset_id__exact=query_string) | Q(description__icontains=query_string) | Q(serial_number__exact=query_string))
Q(asset_id__exact=query_string.upper()) | Q(description__icontains=query_string) | Q(serial_number__exact=query_string))
else:
queryset = self.model.objects.filter(Q(asset_id__exact=query_string))
queryset = self.model.objects.filter(Q(asset_id__exact=query_string.upper()))
if form.cleaned_data['category']:
queryset = queryset.filter(category__in=form.cleaned_data['category'])

View File

@@ -1,7 +1,7 @@
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{page_title}}{% block title %}{% endblock %}</h4>
<h4 class="modal-title">{{page_title|safe}}{% block title %}{% endblock %}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>

View File

@@ -45,8 +45,8 @@
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action text-info" href="{% url 'trainee_detail' %}"><span class="fas fa-file-signature align-middle"></span><span class="align-middle"> My Training Record</span></a>
<a class="list-group-item list-group-item-action" href="{% url 'trainee_list' %}"><span class="fas fa-users"></span> Trainee List</a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-layer-group"></span> Level List</a></a>
<a class="list-group-item list-group-item-action" href="{% url 'session_log' %}"><span class="fas fa-sitemap"></span> Item List</a></a>
<a class="list-group-item list-group-item-action" href="{% url 'level_list' %}"><span class="fas fa-layer-group"></span> Level List</a></a>
<a class="list-group-item list-group-item-action" href="{% url 'item_list' %}"><span class="fas fa-sitemap"></span> Item List</a></a>
</div>
</div>
</div>

View File

@@ -8,7 +8,7 @@ from django.urls import reverse
@reversion.register # profile is already registered, but this triggers my custom versioning logic
class Trainee(Profile):
class Trainee(Profile, RevisionMixin):
class Meta:
proxy = True
@@ -214,7 +214,7 @@ class TrainingLevel(models.Model, RevisionMixin):
@reversion.register
class TrainingLevelRequirement(models.Model):
class TrainingLevelRequirement(models.Model, RevisionMixin):
level = models.ForeignKey('TrainingLevel', related_name='requirements', on_delete=models.RESTRICT)
item = models.ForeignKey('TrainingItem', on_delete=models.RESTRICT)
depth = models.IntegerField(choices=TrainingItemQualification.CHOICES)
@@ -229,7 +229,7 @@ class TrainingLevelRequirement(models.Model):
@reversion.register
class TrainingLevelQualification(models.Model):
class TrainingLevelQualification(models.Model, RevisionMixin):
trainee = models.ForeignKey('Trainee', related_name='levels', on_delete=models.RESTRICT)
level = models.ForeignKey('TrainingLevel', on_delete=models.RESTRICT)
confirmed_on = models.DateTimeField(null=True)

View File

@@ -1,4 +1,4 @@
{% extends 'base_training.html' %}
{% extends request.is_ajax|yesno:'base_ajax.html,base_training.html' %}
{% load static %}
{% load widget_tweaks %}
@@ -34,6 +34,14 @@
<label for="depth" class="col-sm-2 col-form-label">Depth</label>
{% render_field form.depth|add_class:'form-control col-sm'|attr:'required' %}
</div>
<input type="submit" class="btn btn-primary">
{% if not request.is_ajax %}
<button type="submit" class="btn btn-primary">Save</button>
{% endif %}
</form>
{% endblock %}
{% block footer %}
<div class="col-sm-12 text-right pr-0">
<button type="submit" class="btn btn-primary" title="Save" form="requirement-form"><span class="fas fa-save align-middle"></span> <span class="d-none d-sm-inline align-middle">Save</span></button>
</div>
{% endblock %}

View File

@@ -2,11 +2,50 @@
{% load user_has_qualification from tags %}
{% load user_level_if_present from tags %}
{% load static %}
{% block css %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/selects.css' %}"/>
{% endblock %}
{% block preload_js %}
{{ block.super }}
<script src="{% static 'js/selects.js' %}"></script>
{% endblock %}
{% block js %}
<script src="{% static 'js/autocompleter.js' %}"></script>
<script src="{% static 'js/tooltip.js' %}"></script>
<script>
$('document').ready(function(){
$('#requirement_button').click(function (e) {
e.preventDefault();
var url = $(this).attr("href");
$.ajax({
url: url,
success: function(){
$link = $(this);
// Anti modal inception
if ($link.parents('#modal').length === 0) {
modaltarget = $link.data('target');
modalobject = "";
$('#modal').load(url, function (e) {
$('#modal').modal();
$(".selectpicker").selectpicker().each(function(){initPicker($(this))});
});
}
}
});
});
});
</script>
{% endblock %}
{% block content %}
{% if request.user.is_supervisor or perms.training.change_traininglevel %}
<div class="col-sm-12 text-right pr-0">
<a type="button" class="btn btn-success mb-3" href="{% url 'add_requirement' pk=object.pk %}">
<a type="button" class="btn btn-success mb-3" href="{% url 'add_requirement' pk=object.pk %}" id="requirement_button">
<span class="fas fa-plus"></span> Add New Requirement
</a>
</div>

View File

@@ -45,10 +45,10 @@
{% block content %}
<div class="row">
<div class="col-sm-12 text-right">
<a type="button" class="btn btn-success" href="{% url 'edit_record' object.pk %}" id="add_record">
<span class="fas fa-plus"></span> Add New Training Record
</a>
<a href="{% url 'trainee_item_detail' object.pk %}" class="btn btn-info"><span class="fas fa-info-circle"></span> View Detailed Record</a><br/>
<a type="button" class="btn btn-success" href="{% url 'edit_record' object.pk %}" id="add_record">
<span class="fas fa-plus"></span> Add New Training Record
</a>
<a href="{% url 'trainee_item_detail' object.pk %}" class="btn btn-info"><span class="fas fa-info-circle"></span> View Detailed Record</a><br/>
</div>
</div>
<div class="row mb-3">
@@ -59,7 +59,7 @@
<h3 class="card-header"><a href="{% url 'level_detail' qual.level.pk %}">{{ qual }}</a></h3>
<div class="card-footer text-right pr-1">
{% if qual.confirmed_by is None %}
{% if request.user.is_supervisor or request.user.is_superuser %}
{% if request.user.pk != object.pk and request.user.is_supervisor %}
<span class="badge badge-warning">Awaiting Confirmation</span> <a class="btn btn-info" href="{% url 'confirm_level' object.pk qual.level.pk %}">Confirm</a>
{% else %}
<button class="btn btn-warning" disabled>Awaiting Confirmation</button>
@@ -71,7 +71,7 @@
</div>
{% if forloop.last %}</div>{%endif%}
{% empty %}
<span class="badge badge-warning">No qualifications in any levels yet...did someone forget to fill out the paperwork?</span>
<div class="alert alert-warning">No qualifications in any levels yet...did someone forget to fill out the paperwork?</div>
{% endfor %}
<div class="card-columns">
{% for level in started_levels %}

View File

@@ -22,7 +22,7 @@ class ItemList(generic.ListView):
return context
class TraineeDetail(views.ProfileDetail, ModalURLMixin):
class TraineeDetail(views.ProfileDetail):
template_name = "trainee_detail.html"
model = models.Trainee
@@ -41,9 +41,6 @@ class TraineeDetail(views.ProfileDetail, ModalURLMixin):
context[str(i)] = self.object.get_records_of_depth(i)
return context
def get_success_url(self):
return self.get_close_url('trainee_detail', 'trainee_detail')
class TraineeItemDetail(generic.ListView):
model = models.TrainingItemQualification
@@ -58,6 +55,17 @@ class TraineeItemDetail(generic.ListView):
return context
class LevelDetail(generic.DetailView):
template_name = "level_detail.html"
model = models.TrainingLevel
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = "Training Level {} <span class='badge badge-{} badge-pill'><span class='fas fa-{}'></span></span>".format(self.object, self.object.get_department_colour(), self.object.icon)
context["users_with"] = map(lambda qual: qual.trainee, models.TrainingLevelQualification.objects.filter(level=self.object))
return context
class LevelList(generic.ListView):
model = models.TrainingLevel
template_name = "level_list.html"
@@ -90,7 +98,7 @@ class SessionLog(generic.FormView):
return context
class AddQualification(generic.CreateView):
class AddQualification(generic.CreateView, ModalURLMixin):
template_name = "edit_training_record.html"
model = models.TrainingItemQualification
form_class = forms.QualificationForm
@@ -106,7 +114,7 @@ class AddQualification(generic.CreateView):
return context
def get_success_url(self):
return reverse_lazy('trainee_detail', kwargs={"pk": self.object.pk})
return self.get_close_url('trainee_detail', 'trainee_detail')
def get_form_kwargs(self):
kwargs = super(AddQualification, self).get_form_kwargs()
@@ -114,8 +122,8 @@ class AddQualification(generic.CreateView):
return kwargs
class AddLevelRequirement(generic.CreateView):
template_name = "edit_training_level.html"
class AddLevelRequirement(generic.CreateView, ModalURLMixin):
template_name = "add_level_requirement.html"
model = models.TrainingLevelRequirement
form_class = forms.RequirementForm
@@ -130,7 +138,7 @@ class AddLevelRequirement(generic.CreateView):
return kwargs
def get_success_url(self):
return reverse_lazy('level_detail', kwargs={"pk": self.kwargs['pk']})
return self.get_close_url('level_detail', 'level_detail')
@transaction.atomic()
@reversion.create_revision()
@@ -140,17 +148,6 @@ class AddLevelRequirement(generic.CreateView):
return super().form_valid(form, *args, **kwargs)
class LevelDetail(generic.DetailView):
template_name = "level_detail.html"
model = models.TrainingLevel
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["page_title"] = "Training Level {} <span class='badge badge-{} badge-pill'><span class='fas fa-{}'></span></span>".format(self.object, self.object.get_department_colour(), self.object.icon)
context["users_with"] = map(lambda qual: qual.trainee, models.TrainingLevelQualification.objects.filter(level=self.object))
return context
class RemoveRequirement(generic.DeleteView):
model = models.TrainingLevelRequirement
template_name = 'traininglevelrequirement_confirm_delete.html'