Luke Plant avatar Luke Plant committed dab3d67

Beginnings of booking secretary reports page

Comments (0)

Files changed (5)

cciw/cciwmain/static/css/adminextra.css

 .actionable {
     background-color: #ffd0d0;
 }
+
+table.data th,
+table.data td {
+    border: 1px solid #ddd;
+}
+
+table.data th {
+    text-align: center;
+    vertical-align: middle;
+    background-color: #f8f8f8;
+}

cciw/officers/urls.py

     (r'^add-officer/$', 'create_officer'),
     (r'^files/(.*)$', 'officer_files'),
     url(r'^info/$', 'officer_info', name="cciw.officers.views.info"),
+    (r'^booking-reports/(?P<year>\d{4})/$', 'booking_secretary_reports'),
 )

cciw/officers/views.py

 import urlparse
 
 from django import forms
+from django.db.models import F, Sum
 from django.conf import settings
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.auth import REDIRECT_FIELD_NAME
 
 
 camp_admin_required = user_passes_test_improved(is_camp_admin)
-
+booking_secretary_required = user_passes_test_improved(is_booking_secretary)
 
 
 def _camps_as_admin_or_leader(user):
         return dict(show_wiki_link=is_wiki_user(self.request.user))
 
 
+def booking_secretary_reports(request, year=None):
+    from cciw.bookings.models import SEX_MALE, SEX_FEMALE, Booking, BOOKING_BOOKED, BOOKING_CANCELLED, BookingAccount
+    year = int(year)
+    camps = Camp.objects.filter(year=year).prefetch_related('bookings')
+    # Do some filtering in Python to avoid multiple db hits
+    for c in camps:
+        c.confirmed_bookings = [b for b in c.bookings.all() if b.confirmed_booking()]
+        c.confirmed_bookings_boys = [b for b in c.confirmed_bookings if b.sex == SEX_MALE]
+        c.confirmed_bookings_girls = [b for b in c.confirmed_bookings if b.sex == SEX_FEMALE]
+
+
+    # Duplication of business logic here, for performance:
+    payable = BookingAccount.objects.all()
+    # Booked or cancelled places are included.
+    payable = payable.filter(bookings__state=BOOKING_BOOKED) | payable.filter(bookings__state=BOOKING_CANCELLED)
+    # annotation works over the bookings filtered above
+    outstanding = payable.annotate(total_amount_due=Sum('bookings__amount_due')).exclude(total_amount_due=F('total_received'))
+
+    total_amount_due_dict = dict((o.id, o.total_amount_due) for o in outstanding)
+
+    # This will actually exclude people who have outstanding fees but do not
+    # have bookings this year. That's OK - previous year's report page will catch them.
+    bookings = Booking.objects.filter(camp__year__exact=year,
+                                      account__in=[o.id for o in outstanding])
+    bookings = bookings.order_by('account__name','first_name','last_name')
+    # Decorate with the already calculated 'total_amount_due'
+    for b in bookings:
+        b.account.calculated_balance = total_amount_due_dict[b.account_id] - b.account.total_received
+
+    return render(request, 'cciw/officers/booking_secretary_reports.html',
+                  {'year': year, 'camps': camps,
+                   'bookings': bookings})
+
+
+
 officer_info = staff_member_required(OfficerInfo.as_view())

templates/cciw/officers/booking_secretary_reports.html

+{% extends "cciw/officers/base.html" %}
+{% load url from future %}
+
+{% block title %}Bookings {{ year }} | CCIW Officers{% endblock %}
+
+{% block content %}
+<h1>Bookings {{ year }}</h1>
+
+<h2>Camps</h2>
+<table class="data">
+  <tr>
+    <th rowspan=2>Camp</th>
+    <th rowspan=2>Booked</th>
+    <th colspan=3>Confirmed</th>
+  </tr>
+  <tr>
+    <th>All</th>
+    <th>Boys</th>
+    <th>Girls</th>
+  </tr>
+
+
+{% for camp in camps %}
+  <tr>
+    <td>{{ camp }}</td>
+    <td>{{ camp.bookings.all|length }}</td>
+    <td>{{ camp.confirmed_bookings|length }}</td>
+    <td>{{ camp.confirmed_bookings_boys|length }}</td>
+    <td>{{ camp.confirmed_bookings_girls|length }}</td>
+  </tr>
+
+{% endfor %}
+</table>
+
+
+<h2>Outstanding fees</h2>
+<table class="data">
+  <tr>
+    <th>Account</th>
+    <th>Email</th>
+    <th>Phone</th>
+    <th>Balance due</th>
+    <th>Camper name</th>
+    <th>Camp</th>
+    <th>Place cost</th>
+  </tr>
+
+  {% for b in bookings %}
+  <tr>
+    {% ifchanged b.account.id %}
+    <td>{{ b.account.name }}</td>
+    <td>{{ b.account.email }}</td>
+    <td>{{ b.account.phone_number }}</td>
+    <td>{{ b.account.calculated_balance }}</td>
+    {% else %}
+    <td colspan=4></td>
+    {% endifchanged %}
+    <td>{{ b.name }}</td>
+    <td>{{ b.camp.number }}</td>
+    <td>{{ b.amount_due }}</td>
+  </tr>
+  {% endfor %}
+
+</table>
+{% endblock %}

templates/cciw/officers/index.html

 <ul>
   {% if show_booking_secretary_links %}
   <li><a href="/admin/bookings/">Manage bookings</a></li>
+  <li><a href="{% url 'cciw.officers.views.booking_secretary_reports' thisyear %}">Booking reports</a></li>
   {% endif %}
   <li><a href="{% url 'cciw.officers.views.applications' %}">Submit/view applications</a></li>
   <li><a href="{% url 'cciw.officers.views.info' %}">Information about camp</a></li>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.