diff --git a/billing_week.py b/billing_week.py index 63397ee..cb225ee 100644 --- a/billing_week.py +++ b/billing_week.py @@ -292,6 +292,39 @@ def parse_billing_week(billing_week_string): week, ) +def next_n_billing_weeks(n, bw, billing_weeks=None): + """ + Takes a number of billing weeks and a starting billing week, and returns + a list of billing weeks + """ + if billing_weeks is None: + billing_weeks = [] + if n > len(billing_weeks): + billing_weeks.append(bw.next()) + return next_n_billing_weeks(n, bw.next(), billing_weeks) + else: + return billing_weeks + +def prev_n_billing_weeks(n, bw, billing_weeks=None): + if billing_weeks is None: + billing_weeks = [] + if n > len(billing_weeks): + billing_weeks.append(bw.prev()) + return prev_n_billing_weeks(n, bw.prev(), billing_weeks) + else: + return billing_weeks + + +def next_valid_billing_week(bw, billing_week_strings): + """ + Takes a billing week, and a set of skipped billing weeks, and steps forward + until it finds a week that isni't in the skipped set + """ + if str(bw) in billing_week_strings: + return next_valid_billing_week(bw.next(), billing_week_strings) + else: + return bw + if __name__ == '__main__': @@ -539,4 +572,26 @@ def test_billing_weeks_left_in_month(self): self.assertEqual(len( billing_weeks_left_in_the_month("2016-06 1")), 4) + def test_next_n_billing_weeks_returns_right_no_of_billing_weeks(self): + utc = pytz.timezone("UTC") + s = utc.localize(datetime.datetime(2016, 9, 2, 0)) + last_bw_in_aug = get_billing_week(s) + self.assertEqual( + len(next_n_billing_weeks(5, last_bw_in_aug)), 5) + + def test_prev_n_billing_weeks_returns_next_billing_week(self): + utc = pytz.timezone("UTC") + s = utc.localize(datetime.datetime(2016, 9, 2, 0)) + last_bw_in_aug = get_billing_week(s) + self.assertNotIn( + last_bw_in_aug, + prev_n_billing_weeks(5, last_bw_in_aug)) + + def test_prev_n_billing_weeks_returns_right_no_of_billing_weeks(self): + utc = pytz.timezone("UTC") + s = utc.localize(datetime.datetime(2016, 9, 2, 0)) + last_bw_in_aug = get_billing_week(s) + self.assertEqual( + len(prev_n_billing_weeks(5, last_bw_in_aug)), 5) + unittest.main() diff --git a/blueworld/settings.py b/blueworld/settings.py index d75f083..c8153d9 100644 --- a/blueworld/settings.py +++ b/blueworld/settings.py @@ -51,6 +51,7 @@ 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', + 'django.contrib.humanize', 'django.contrib.staticfiles', # The Django sites framework is required for allauth 'django.contrib.sites', @@ -71,6 +72,7 @@ if DEBUG: INSTALLED_APPS.append('django_extensions') + INSTALLED_APPS.append('debug_toolbar') RQ_QUEUES = { diff --git a/blueworld/static/blueworld/application.js b/blueworld/static/blueworld/application.js index 5d62f30..218d2f4 100644 --- a/blueworld/static/blueworld/application.js +++ b/blueworld/static/blueworld/application.js @@ -18995,3 +18995,13 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons // Trigger foundation to add the effects we want $(document).foundation(); + + +$(document).ready(function() { + $('.collection-point-info .instructions').hide() + $('.collection-point-info a').click(function(event) { + + $(this).parent().find('.instructions').slideToggle("fast"); + + }); +}); diff --git a/blueworld/static/blueworld/collection-map.js b/blueworld/static/blueworld/collection-map.js new file mode 100644 index 0000000..da0ae62 --- /dev/null +++ b/blueworld/static/blueworld/collection-map.js @@ -0,0 +1,62 @@ +jQuery(document).ready(function($) { + var mymap = L.map('collection-map').setView([51.557853, -0.073096], 13); + var popup = L.popup(); + + L.tileLayer('https://api.mapbox.com/styles/v1/mapbox/streets-v9/tiles/256/{z}/{x}/{y}?access_token=pk.eyJ1IjoibXJjaHJpc2FkYW1zIiwiYSI6InFobnRZRzQifQ.jLPeG4HV4RiCL8RVNPBTwg', { + attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © Mapbox', + maxZoom: 18, + }).addTo(mymap); + + function showLocation(e) { + // update popup to show name + var p = this.popup, loc = this.loc, mymap = this.leafletmap; + p.setContent("

" + loc.name + "

" + loc.location + "

"); + p.setLatLng(e.latlng); + p.openOn(mymap); + } + + // accepts a set of coordinates and returns a leaflet marker if they're + // usable + function createMarker(loc) { + + // check for None's coming through from django + if (loc.longitude === "None" || loc.latitude == "None"){ + return false + } + // if we have both coords, we can create a marker + if (loc.longitude && loc.latitude) { + return L.marker([loc.latitude, loc.longitude]) + } + // otherwise assume we didn't have the required attributes + return false + } + + GC.markers = {} + GC.activeMarker = ""; + + $.each(GC.locations, function(k, loc) { + // only try to place a marker if we have coords to place with + newMarker = createMarker(loc) + if (newMarker) { + newMarker + .addTo(mymap) + .bindPopup("

" + loc.name + "

" + loc.location + "

"); + GC.markers[k] = { id: k, marker: newMarker}; + } + + // add listener on the inputs + $('.collection-points ul li').on('mouseover', function(event) { + var marker = $(this).find('input').attr('id'); + + if (marker !== GC.activeMarker){ + GC.activeMarker = marker; + GC.markers[marker].marker.fire('click'); + } + }) + }) + + + // add listener to find marker with the `id_collection_point_0` id, + // so it is highlighted when selected. + +}); diff --git a/blueworld/static/blueworld/style.css b/blueworld/static/blueworld/style.css index 7e9b26d..e58d5ad 100644 --- a/blueworld/static/blueworld/style.css +++ b/blueworld/static/blueworld/style.css @@ -2000,6 +2000,7 @@ select { display: block; margin-right: 0; } } +.errorlist, .callout { margin: 0 0 1rem 0; padding: 1rem; @@ -3519,7 +3520,7 @@ table.hover tr:nth-of-type(even):hover { h1, h2, h3, .font-display { font-family: "GC Display", "Impact", "Arial", sans-serif; -} + text-transform: uppercase; } .font-text { font-family: "Domaine Text Regular", "Times New Roman", Georgia, serif; @@ -3543,6 +3544,9 @@ a { .global-nav { margin-bottom: 1rem; } +#billing-dates li.past p { + color: #e6e6e6; } + .tagline { display: inline; } @@ -3554,13 +3558,14 @@ a { text-align: center; } .header .logo { max-width: 75px; - margin-right: 30px; - margin-left: 20px; - margin-top: 20px; } + margin-right: 1rem; + margin-left: 1rem; + margin-top: 1rem; + margin-bottom: 1rem; } .header h2 { text-transform: uppercase; display: inline-block; - font-size: 200%; + font-size: 3rem; margin-top: 0.5em; } .header nav .menu { background-color: #ccc; } @@ -3573,6 +3578,15 @@ a { h3.font-text { max-width: 30em; } +.current-choice, +.this_week { + background: #EEE; + padding: 1rem; + margin-bottom: 1rem; } + +.next_week { + padding: 1rem; } + fieldset.collection-points label { font-size: 120%; font-weight: bold; } @@ -3595,6 +3609,26 @@ fieldset.collection-points ul > li.columns:last-child:not(:first-child) { padding: 0.5rem; margin-left: 0; } +.this_week p, +.this_week h4{ + font-size: 1.5rem; +} +.this_week .instructions p{ + font-size: 1rem; +} + +.this_week ul li { + font-size: 1.5rem; } + +.instructions { + padding: 1rem; + font-size: 1rem; + max-width: 40rem; + border-top: 1px solid #cacaca; + background-color: #e6e6e6; } + .instructions p { + max-width: 40em; } + .callout { background-color: #e6e6e6; } @@ -3607,6 +3641,11 @@ fieldset.collection-points ul > li.columns:last-child:not(:first-child) { margin-right: auto; } -#billing-dates li.past p{ - color:grey; +div.skipped h5, +div.skipped li{ + color: #999; +} + +.greeting{ + font-size:2em; } diff --git a/blueworld/templates/admin/join/customer/change_list.html b/blueworld/templates/admin/join/customer/change_list.html new file mode 100644 index 0000000..f344b14 --- /dev/null +++ b/blueworld/templates/admin/join/customer/change_list.html @@ -0,0 +1,57 @@ +{% extends "admin/change_list.html" %} +{% load i18n admin_urls admin_static admin_list %} + +{% block content %} + + {% include "partials/dashboard_stats.html" %} + + +
+ {% block object-tools %} + {% if has_add_permission %} + + {% endif %} + {% endblock %} + {% if cl.formset.errors %} +

+ {% if cl.formset.total_error_count == 1 %}{% trans "Please correct the error below." %}{% else %}{% trans "Please correct the errors below." %}{% endif %} +

+ {{ cl.formset.non_form_errors }} + {% endif %} +
+ {% block search %}{% search_form cl %}{% endblock %} + {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %} + + {% block filters %} + {% if cl.has_filters %} +
+

{% trans 'Filter' %}

+ {% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %} +
+ {% endif %} + {% endblock %} + +
{% csrf_token %} + {% if cl.formset %} +
{{ cl.formset.management_form }}
+ {% endif %} + + {% block result_list %} + {% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %} + {% result_list cl %} + {% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %} + {% endblock %} + {% block pagination %}{% pagination cl %}{% endblock %} +
+
+
+{% endblock %} diff --git a/blueworld/templates/base.html b/blueworld/templates/base.html index 4a2128b..a2dd632 100644 --- a/blueworld/templates/base.html +++ b/blueworld/templates/base.html @@ -45,8 +45,11 @@ - {% include "partials/footer.html" %} - + {% include "partials/footer.html" %} + + {% block extra_scripts %} + {% endblock %} + diff --git a/blueworld/templates/collection_point.html b/blueworld/templates/collection_point.html index 54f81f7..4919fb6 100644 --- a/blueworld/templates/collection_point.html +++ b/blueworld/templates/collection_point.html @@ -1,5 +1,5 @@ {% extends "base_join.html" %} - +{% load staticfiles %} {% block head_title %}Join{% endblock %} {% block extra_head %} + +
+ +
{% if form.non_form_errors or form.errors %}
@@ -25,6 +31,9 @@

Where do you want to collect your bag from?

+ + +
{% csrf_token %}
@@ -53,3 +62,7 @@

Where do you want to collect your bag from?

{% endblock %} {% block extra_body %} {% endblock %} + +{% block extra_scripts %} + +{% endblock %} diff --git a/blueworld/templates/dashboard/change-collection-point.html b/blueworld/templates/dashboard/change-collection-point.html index cc0be61..a394901 100644 --- a/blueworld/templates/dashboard/change-collection-point.html +++ b/blueworld/templates/dashboard/change-collection-point.html @@ -1,5 +1,5 @@ {% extends "base.html" %} - +{% load staticfiles %} {% block head_title %}Change Collection Point{% endblock %} {% block extra_head %} + +
+ +
{{ form.non_form_errors }} @@ -57,3 +80,6 @@

Change Collection Point

{% endblock %} {% block extra_body %} {% endblock %} +{% block extra_scripts %} + +{% endblock %} diff --git a/blueworld/templates/dashboard/change-order.html b/blueworld/templates/dashboard/change-order.html index acc967e..6a45ff4 100644 --- a/blueworld/templates/dashboard/change-order.html +++ b/blueworld/templates/dashboard/change-order.html @@ -6,11 +6,18 @@

Change Order

-

Your current order is:

+
If we haven't yet placed the week's order with our growers, you can change your order below, and we'll use that instead.
+
If the week's order has already been sent, changes will affect the following week.
+ + +
+ + +

Your current order is:

    {% for bag_quantity in request.user.customer.bag_quantities %}
  • - {{ bag_quantity.quantity }} x {{ bag_quantity.bag_type.name }} +

    {{ bag_quantity.quantity }} x {{ bag_quantity.bag_type.name }}

    {% if not bag_quantity.bag_type.active %} (note this item is no longer available so if you change your order below, you will not be able to re-select it in future) {% endif %} @@ -18,8 +25,9 @@

    Change Order

    {% endfor %}
-

New Order

+
+

New Order

Note: You can only order a small fruit bag if you are also ordering another bag.

diff --git a/blueworld/templates/dashboard/index.html b/blueworld/templates/dashboard/index.html index 3619ba7..00a59a6 100644 --- a/blueworld/templates/dashboard/index.html +++ b/blueworld/templates/dashboard/index.html @@ -4,55 +4,160 @@ {% block extra_head %}{% endblock %} {% block content %}
-

Dashboard

-

Welcome {{ request.user.customer.nickname }}!

- -

Your collection point is - {{ collection_point.name }} - {% if skipped %} - but your next collection is skipped so there is nothing for you to pick up - {{ collection_date }}. -

- {% else %} - and your next collection is - {{ collection_date }}. -

-

Please check the opening days and hours of your collection point before collecting your order.

- {% endif %} + + + +

Dashboard

+
+

Hi {{ request.user.customer.nickname }}!

-
+

Your Bags

+ {% for col in collections %} -

Your Order

-
    - {% for bag_quantity in bag_quantities %} -
  • {{ bag_quantity.quantity }} x {{ bag_quantity.bag_type.name }}
  • - {% endfor %} -
+
+
+ Week commencing {{ col.billing_week.mon }}: +
+ + + {% if new_customer and col.billing_week == this_bw %} +

Welcome aboard! This week's order has already gone to the farmers, and your collections will start next week.

+
In the meantime, we'd love to know a bit more about how you found us.
+
Perhaps you could help us with this new member survey here?
+ {% elif col.skipped %} + +
+ You've marked this week as on holiday, so there's no collection to pick up. +
-

Any changes you make until {{ deadline }} will affect {{ changes_affect }}

+ {% else %} + +
+ Available on + + {% for d in col.dates %} + {% if forloop.counter == 2 %} + and + {% endif %} + {{ d }}{% endfor %}, + + + from + {{ col.collection_point }}: +
+ + +
    + {% for bag_quantity in col.bags %} +
  • {{ bag_quantity.quantity }} x {{ bag_quantity.bag_type.name }}
  • + {% endfor %} +
+ + {% endif %} + + {% if col.collection_point.instructions and col.billing_week == this_bw %} + {% if not new_customer %} +
+ See collection point instructions +
+ {{ col.collection_point.instructions|linebreaks }} +
+
+ {% endif %} + {% endif %} -
+ + {% if forloop.first %} +
Upcoming collections
{% endif %} -
  • Change Order
  • -
  • Change Collection Point
  • -
  • Leave
  • - + + {% endfor %} + + + + + +
    + +
    + +
    On holiday soon? Want to try a different bag? Let us know when you're away, or change your order below.
    +
    + + + + +
    -
    +
    diff --git a/blueworld/templates/dashboard/order-history.html b/blueworld/templates/dashboard/order-history.html index d2d2aa5..594c93f 100644 --- a/blueworld/templates/dashboard/order-history.html +++ b/blueworld/templates/dashboard/order-history.html @@ -58,7 +58,12 @@

    {{ month|date:"F Y" }}

    {% endif %} {% elif event.type == 'COLLECTION_POINT_CHANGE' %} Collection point changed to {{ event.new_collection_point }}.
    - Please start collecting here from {{ event.bw.next.wed|date:"DATE_FORMAT" }}. + {% comment %} + # if the collection point's dates change, then it will look + # wrong in the history, so best not to show it at all + Please start collecting here from {{ event.bw.next.wed|date:"DATE_FORMAT" }} + {% endcomment %} + {% elif event.type == 'ACCOUNT_STATUS_CHANGE' %} Account status changed to {{ event.new_account_status }}. {% elif event.type == 'OUT_OF_CYCLE_PAYMENT' %} diff --git a/blueworld/templates/dashboard/skip-weeks.html b/blueworld/templates/dashboard/skip-weeks.html index d02a4b2..9a402e7 100644 --- a/blueworld/templates/dashboard/skip-weeks.html +++ b/blueworld/templates/dashboard/skip-weeks.html @@ -1,12 +1,20 @@ {% extends "base.html" %} -{% block head_title %}Skip Weeks{% endblock %} +{% block head_title %}Holidays{% endblock %} {% block extra_head %}{% endblock %} {% block content %}
    -

    Skip Weeks

    +

    Holidays

    -

    If you want to skip a week further in the future than those listed below. Please contact help@growingcommunities.org.

    + + +

    Won't be able to make a collection because you're away on holiday? Mark the weeks you wont be around, and we'll refund the cost of those weeks in your next bill.

    {{ formset.non_form_errors }} @@ -18,7 +26,7 @@

    Skip Weeks

    - Skip collection the week starting: + Skip my collection for the weeks starting: @@ -49,6 +57,9 @@

    Skip Weeks

    {% endfor %} + +

    If you want to skip a week further in the future than those listed below, let us know at help@growingcommunities.org.

    +
    diff --git a/blueworld/templates/partials/dashboard_stats.html b/blueworld/templates/partials/dashboard_stats.html new file mode 100644 index 0000000..2c5b6a3 --- /dev/null +++ b/blueworld/templates/partials/dashboard_stats.html @@ -0,0 +1,19 @@ + + + + diff --git a/blueworld/templates/partials/footer.html b/blueworld/templates/partials/footer.html index f2804f8..f45e1b9 100644 --- a/blueworld/templates/partials/footer.html +++ b/blueworld/templates/partials/footer.html @@ -9,9 +9,9 @@ {% if not request.user.username and request.path_info == '/' %} Staff Login. {% endif %} - It is {% now "jS F Y H:i" %} UTC. + It is {% now "D jS F Y H:i" %} UTC. {% timezone "Europe/London" %} - Local time: {% now "jS F Y H:i" %}. + Local time: {% now "D jS F Y H:i" %}. {% endtimezone %}

    diff --git a/blueworld/templates/partials/navigation.html b/blueworld/templates/partials/navigation.html index 56c4d27..d35c727 100644 --- a/blueworld/templates/partials/navigation.html +++ b/blueworld/templates/partials/navigation.html @@ -1,18 +1,17 @@ {% if user.is_authenticated %} diff --git a/blueworld/urls.py b/blueworld/urls.py index f46c1a7..04e54b3 100644 --- a/blueworld/urls.py +++ b/blueworld/urls.py @@ -74,6 +74,11 @@ r'^admin/', admin.site.urls, ), + url( + r'^admin/pickup_list/(?P[0-9]{4})/(?P[0-9]{1,2})/(?P[0-9]{1,2})/$', + join.views.generate_pickup_list, + name='generate_pickup_list', + ), url( r'^hijack/', include('hijack.urls') diff --git a/exports/models.py b/exports/models.py index 7ea1165..3975258 100644 --- a/exports/models.py +++ b/exports/models.py @@ -104,13 +104,15 @@ def report_mailchimp_header_row(cls): Returns header for for mailchimp export """ header_row = [ - 'first_name', - 'surname', + 'full_name', + 'nickname', 'email', 'status', - 'pickup', + 'collection', 'bags', - 'on_holiday' + 'on_holiday', + 'start_date', + 'stop_date' ] for bag_type in BagType.objects.all(): header_row.append(bag_type.name) @@ -139,6 +141,10 @@ def report_mailchimp(cls): render_bag_quantities(bq), # on_holiday customer.skipped, + # start_date + customer.start_date, + # stop date + customer.leaving_date ] quantities = {} for bq_ in bq: diff --git a/features/dashboard-collection-skips.feature b/features/dashboard-collection-skips.feature index 5a154fe..6b37783 100644 --- a/features/dashboard-collection-skips.feature +++ b/features/dashboard-collection-skips.feature @@ -28,39 +28,63 @@ Feature: Dashboard Collection Skips And I switch to the user browser And I login with "dashboard-collection-skips@example.com" and "123123ab" When I navigate to /dashboard - Then I see "but your next collection is skipped so there is nothing for you to pick up" in "#message" - And I see "" in "#collection-date" + Then I see "nothing to pick up" in "#message" + And I see "" in "#skipped-collection-date" And I see "" in "#deadline" And I see "" in "#changes-affect" + # you' + # 17th is a Sunday. IMPORTANT: Notice the behaviour on Thursday depends on the collection point chosen. # NOTE: All the times are in UTC, so are an hour behind what you'd expect in BST for July Examples: Times where you can make changes for the next collection - | date | day | collection_day | week_to_skip | skipped_collection_date | deadline | changes_affect | - | 2016-07-17 14:00:00 | Sunday | Wednesday | #id_form-0-skipped | on Wednesday | 3pm next Sunday | the collection after next | - | 2016-07-17 14:00:00 | Sunday | Thursday | #id_form-0-skipped | on Thursday | 3pm next Sunday | the collection after next | - | 2016-07-17 14:00:00 | Sunday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday | 3pm next Sunday | the collection after next | - | 2016-07-18 00:00:00 | Monday | Wednesday | #id_form-0-skipped | on Wednesday | 3pm this Sunday | next week's collection | - | 2016-07-18 00:00:00 | Monday | Thursday | #id_form-0-skipped | on Thursday | 3pm this Sunday | next week's collection | - | 2016-07-18 00:00:00 | Monday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday | 3pm this Sunday | next week's collection | - | 2016-07-19 00:00:00 | Tuesday | Wednesday | #id_form-0-skipped | tomorrow | 3pm this Sunday | next week's collection | - | 2016-07-19 00:00:00 | Tuesday | Thursday | #id_form-0-skipped | on Thursday | 3pm this Sunday | next week's collection | - | 2016-07-19 00:00:00 | Tuesday | Wednesday and Thursday | #id_form-0-skipped | tomorrow or Thursday | 3pm this Sunday | next week's collection | - | 2016-07-20 00:00:00 | Wednesday | Wednesday | #id_form-0-skipped | today | 3pm this Sunday | next week's collection | - | 2016-07-20 00:00:00 | Wednesday | Thursday | #id_form-0-skipped | tomorrow | 3pm this Sunday | next week's collection | - | 2016-07-20 00:00:00 | Wednesday | Wednesday and Thursday | #id_form-0-skipped | today or tomorrow | 3pm this Sunday | next week's collection | - | 2016-07-21 00:00:00 | Thursday | Wednesday | #id_form-0-skipped | on Wednesday next week | 3pm this Sunday | next week's collection | - | 2016-07-21 00:00:00 | Thursday | Thursday | #id_form-0-skipped | today | 3pm this Sunday | next week's collection | - | 2016-07-21 00:00:00 | Thursday | Wednesday and Thursday | #id_form-0-skipped | today | 3pm this Sunday | next week's collection | - | 2016-07-22 00:00:00 | Friday | Wednesday | #id_form-0-skipped | on Wednesday next week | 3pm this Sunday | next week's collection | - | 2016-07-22 00:00:00 | Friday | Thursday | #id_form-0-skipped | on Thursday next week | 3pm this Sunday | next week's collection | - | 2016-07-22 00:00:00 | Friday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday next week | 3pm this Sunday | next week's collection | - | 2016-07-23 00:00:00 | Saturday | Wednesday | #id_form-0-skipped | on Wednesday next week | 3pm tomorrow | next week's collection | - | 2016-07-23 00:00:00 | Saturday | Thursday | #id_form-0-skipped | on Thursday next week | 3pm tomorrow | next week's collection | - | 2016-07-23 00:00:00 | Saturday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday next week | 3pm tomorrow | next week's collection | - | 2016-07-24 13:59:59 | Sunday | Wednesday | #id_form-0-skipped | on Wednesday | 3pm today | next week's collection | - | 2016-07-24 13:59:59 | Sunday | Thursday | #id_form-0-skipped | on Thursday | 3pm today | next week's collection | - | 2016-07-24 13:59:59 | Sunday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday | 3pm today | next week's collection | + | date | day | collection_day | week_to_skip | skipped_collection_date | deadline | changes_affect | + | 2016-07-17 14:00:00 | Sunday | Wednesday | #id_form-0-skipped | Wednesday 20th July | 3pm next Sunday | the collection after next | + | 2016-07-17 14:00:00 | Sunday | Thursday | #id_form-0-skipped | Thursday 21st July | 3pm next Sunday | the collection after next | + | 2016-07-17 14:00:00 | Sunday | Wednesday and Thursday | #id_form-0-skipped | Wednesday 20th July or Thursday 21st July | 3pm next Sunday | the collection after next | + | 2016-07-18 00:00:00 | Monday | Wednesday | #id_form-0-skipped | Wednesday 20th July | 3pm this Sunday | next week's collection | + | 2016-07-18 00:00:00 | Monday | Thursday | #id_form-0-skipped | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-18 00:00:00 | Monday | Wednesday and Thursday | #id_form-0-skipped | Wednesday 20th July or Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-19 00:00:00 | Tuesday | Wednesday | #id_form-0-skipped | Wednesday 20th July | 3pm this Sunday | next week's collection | + | 2016-07-19 00:00:00 | Tuesday | Thursday | #id_form-0-skipped | on Thursday | 3pm this Sunday | next week's collection | + | 2016-07-19 00:00:00 | Tuesday | Wednesday and Thursday | #id_form-0-skipped | Wednesday 20th July or Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-20 00:00:00 | Wednesday | Wednesday | #id_form-0-skipped | Wednesday 20th July | 3pm this Sunday | next week's collection | + | 2016-07-20 00:00:00 | Wednesday | Thursday | #id_form-0-skipped | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-20 00:00:00 | Wednesday | Wednesday and Thursday | #id_form-0-skipped | Wednesday 20th July or Thursday 21st July | 3pm this Sunday | next week's collection | + + + @wip + Scenario Outline: Dashboard information with no skips + # Need to freeze time first, otherwise you get logged out because of the session time of 20 mins + Given I freeze time at + And I switch to the admin browser + And I login as a member of staff + And I navigate to /admin/join/collectionpoint/ + And I follow the "The Old Fire Station" link + And I choose "" from "#id_collection_day" + And I click the "Save" button + And I switch to the user browser + And I login with "dashboard-collection-skips@example.com" and "123123ab" + When I navigate to /dashboard + Then I see "your next collection in " in ".this_week" + And I see "" in "#skipped-collection-date" + And I see "" in "#deadline" + And I see "" in "#changes-affect" + + Examples: Times where we shouldn't see a week as skipped + | date | day | collection_day | week_to_skip | skipped_collection_date | deadline | changes_affect | + | 2016-07-21 00:00:00 | Thursday | Wednesday | #id_form-0-skipped | on Wednesday next week | 3pm this Sunday | next week's collection | + # | 2016-07-21 00:00:00 | Thursday | Thursday | #id_form-0-skipped | today | 3pm this Sunday | next week's collection | + # | 2016-07-21 00:00:00 | Thursday | Wednesday and Thursday | #id_form-0-skipped | today | 3pm this Sunday | next week's collection | + # | 2016-07-22 00:00:00 | Friday | Wednesday | #id_form-0-skipped | on Wednesday next week | 3pm this Sunday | next week's collection | + # | 2016-07-22 00:00:00 | Friday | Thursday | #id_form-0-skipped | on Thursday next week | 3pm this Sunday | next week's collection | + # | 2016-07-22 00:00:00 | Friday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday next week | 3pm this Sunday | next week's collection | + # | 2016-07-23 00:00:00 | Saturday | Wednesday | #id_form-0-skipped | on Wednesday next week | 3pm tomorrow | next week's collection | + # | 2016-07-23 00:00:00 | Saturday | Thursday | #id_form-0-skipped | on Thursday next week | 3pm tomorrow | next week's collection | + # | 2016-07-23 00:00:00 | Saturday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday next week | 3pm tomorrow | next week's collection | + # | 2016-07-24 13:59:59 | Sunday | Wednesday | #id_form-0-skipped | on Wednesday | 3pm today | next week's collection | + # | 2016-07-24 13:59:59 | Sunday | Thursday | #id_form-0-skipped | on Thursday | 3pm today | next week's collection | + # | 2016-07-24 13:59:59 | Sunday | Wednesday and Thursday | #id_form-0-skipped | on Wednesday or Thursday | 3pm today | next week's collection | Scenario: _teardown Given I return to the current time diff --git a/features/dashboard-collection.feature b/features/dashboard-collection.feature index 4bf42f1..81b2a68 100644 --- a/features/dashboard-collection.feature +++ b/features/dashboard-collection.feature @@ -4,6 +4,8 @@ Feature: Dashboard Collection Given I switch to the user browser And I create a started user "Dashboard Collection", "dashboard-collection", "dashboard-collection@example.com" with password "123123ab" + + Scenario Outline: Dashboard information # Need to freeze time first, otherwise you get logged out because of the session time of 20 mins Given I freeze time at @@ -16,7 +18,8 @@ Feature: Dashboard Collection And I switch to the user browser And I login with "dashboard-collection@example.com" and "123123ab" When I navigate to /dashboard - And I see "and your next collection is" in "#message" + + And I see "Your next collection" in "#message" Then I see "" in "#collection-date" And I see "" in "#deadline" And I see "" in "#changes-affect" @@ -24,31 +27,32 @@ Feature: Dashboard Collection # 17th is a Sunday. IMPORTANT: Notice the behaviour on Thursday depends on the collection point chosen. # NOTE: All the times are in UTC, so are an hour behind what you'd expect in BST for July Examples: Times where you can make changes for the next collection - | date | day | collection_day | collection_date | deadline | changes_affect | - | 2016-07-17 13:59:59 | Sunday | Wednesday | Wednesday | 3pm today | next week's collection | - | 2016-07-17 13:59:59 | Sunday | Thursday | Thursday | 3pm today | next week's collection | - | 2016-07-17 13:59:59 | Sunday | Wednesday and Thursday | Wednesday and Thursday | 3pm today | next week's collection | - | 2016-07-17 14:00:00 | Sunday | Wednesday | Wednesday | 3pm next Sunday | the collection after next | - | 2016-07-17 14:00:00 | Sunday | Thursday | Thursday | 3pm next Sunday | the collection after next | - | 2016-07-17 14:00:00 | Sunday | Wednesday and Thursday | Wednesday and Thursday | 3pm next Sunday | the collection after next | - | 2016-07-18 00:00:00 | Monday | Wednesday | Wednesday | 3pm this Sunday | next week's collection | - | 2016-07-18 00:00:00 | Monday | Thursday | Thursday | 3pm this Sunday | next week's collection | - | 2016-07-18 00:00:00 | Monday | Wednesday and Thursday | Wednesday and Thursday | 3pm this Sunday | next week's collection | - | 2016-07-19 00:00:00 | Tuesday | Wednesday | tomorrow | 3pm this Sunday | next week's collection | - | 2016-07-19 00:00:00 | Tuesday | Thursday | Thursday | 3pm this Sunday | next week's collection | - | 2016-07-19 00:00:00 | Tuesday | Wednesday and Thursday | tomorrow and Thursday | 3pm this Sunday | next week's collection | - | 2016-07-20 00:00:00 | Wednesday | Wednesday | today | 3pm this Sunday | next week's collection | - | 2016-07-20 00:00:00 | Wednesday | Thursday | tomorrow | 3pm this Sunday | next week's collection | - | 2016-07-20 00:00:00 | Wednesday | Wednesday and Thursday | today and tomorrow | 3pm this Sunday | next week's collection | - | 2016-07-21 00:00:00 | Thursday | Wednesday | Wednesday next week | 3pm this Sunday | next week's collection | - | 2016-07-21 00:00:00 | Thursday | Thursday | today | 3pm this Sunday | next week's collection | - | 2016-07-21 00:00:00 | Thursday | Wednesday and Thursday | today | 3pm this Sunday | next week's collection | - | 2016-07-22 00:00:00 | Friday | Wednesday | Wednesday next week | 3pm this Sunday | next week's collection | - | 2016-07-22 00:00:00 | Friday | Thursday | Thursday next week | 3pm this Sunday | next week's collection | - | 2016-07-22 00:00:00 | Friday | Wednesday and Thursday | Wednesday and Thursday next week | 3pm this Sunday | next week's collection | - | 2016-07-23 00:00:00 | Saturday | Wednesday | Wednesday next week | 3pm tomorrow | next week's collection | - | 2016-07-23 00:00:00 | Saturday | Thursday | Thursday next week | 3pm tomorrow | next week's collection | - | 2016-07-23 00:00:00 | Saturday | Wednesday and Thursday | Wednesday and Thursday next week | 3pm tomorrow | next week's collection | + | date | day | collection_day | collection_date | deadline | changes_affect | + | 2016-07-17 13:59:59 | Sunday | Wednesday | Wednesday 20th July | 3pm today | next week's collection | + | 2016-07-17 13:59:59 | Sunday | Thursday | Thursday 21st July | 3pm today | next week's collection | + | 2016-07-17 13:59:59 | Sunday | Wednesday and Thursday | Wednesday 20th July and Thursday 21st July | 3pm today | next week's collection | + | 2016-07-17 14:00:00 | Sunday | Wednesday | Wednesday 20th July | 3pm next Sunday | the collection after next | + | 2016-07-17 14:00:00 | Sunday | Thursday | Thursday 21st July | 3pm next Sunday | the collection after next | + | 2016-07-17 14:00:00 | Sunday | Wednesday and Thursday | Wednesday 20th July and Thursday 21st July | 3pm next Sunday | the collection after next | + | 2016-07-18 00:00:00 | Monday | Wednesday | Wednesday 20th July | 3pm this Sunday | next week's collection | + | 2016-07-18 00:00:00 | Monday | Thursday | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-18 00:00:00 | Monday | Wednesday and Thursday | Wednesday 20th July and Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-19 00:00:00 | Tuesday | Wednesday | Wednesday 20th July | 3pm this Sunday | next week's collection | + | 2016-07-19 00:00:00 | Tuesday | Thursday | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-19 00:00:00 | Tuesday | Wednesday and Thursday | Wednesday 20th July and Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-20 00:00:00 | Wednesday | Wednesday | Wednesday 20th July | 3pm this Sunday | next week's collection | + | 2016-07-20 00:00:00 | Wednesday | Thursday | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-20 00:00:00 | Wednesday | Wednesday and Thursday | Wednesday 20th July and Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-21 00:00:00 | Thursday | Wednesday | Wednesday 27th July | 3pm this Sunday | next week's collection | + | 2016-07-21 00:00:00 | Thursday | Thursday | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-21 00:00:00 | Thursday | Wednesday and Thursday | Thursday 21st July | 3pm this Sunday | next week's collection | + | 2016-07-22 00:00:00 | Friday | Wednesday | Wednesday 27th July | 3pm this Sunday | next week's collection | + | 2016-07-22 00:00:00 | Friday | Thursday | Thursday 28th July | 3pm this Sunday | next week's collection | + | 2016-07-22 00:00:00 | Friday | Wednesday and Thursday | Wednesday 27th July and Thursday 28th July | 3pm this Sunday | next week's collection | + | 2016-07-23 00:00:00 | Saturday | Wednesday | Wednesday 27th July | 3pm tomorrow | next week's collection | + | 2016-07-23 00:00:00 | Saturday | Thursday | Thursday 28th July | 3pm tomorrow | next week's collection | + | 2016-07-23 00:00:00 | Saturday | Wednesday and Thursday | Wednesday 27th July and Thursday 28th July | 3pm tomorrow | next week's collection | + Scenario: _teardown Given I return to the current time diff --git a/join/admin.py b/join/admin.py index e9b31ed..503ae02 100644 --- a/join/admin.py +++ b/join/admin.py @@ -1,5 +1,5 @@ import logging - +import datetime from collections import OrderedDict from datetime import timedelta @@ -27,7 +27,14 @@ from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ from hijack import settings as hijack_settings -from join.helper import get_current_request, render_bag_quantities +from join.helper import ( + get_current_request, + render_bag_quantities, + customer_ids_on_holiday_for_billing_week, + effective_billing_week, + friendly_date, + customer_ids_by_status +) from join.models import ( AccountStatusChange, BagType, @@ -52,6 +59,7 @@ logger = logging.getLogger(__name__) + class BlueWorldModelAdmin(admin.ModelAdmin): def has_delete_permission(self, request, obj=None): return False @@ -98,8 +106,10 @@ class CollectionPointAdmin(BlueWorldModelAdmin): def pickup_list(self, request, queryset): now = timezone.now() bw = get_billing_week(now) + # TODO show the next two billable weeks as shortcut options + # the day after a collection day (i.e. Thursday), default to show the next billable week, and the one after it initial = { - 'billing_week': str(bw) + 'billing_week': str(bw.next().wed) } if '_generate' in request.POST: form = pickupListForm(request.POST, initial=initial) @@ -235,13 +245,7 @@ def lookups(self, request, model_admin): return choices def _by_status(self, query_value): - ascs = AccountStatusChange.objects.order_by( - 'customer', '-changed' - ).distinct('customer') - - customer_ids = [ - c.customer_id for c in ascs if c.status == query_value - ] + customer_ids = customer_ids_by_status(query_value) return customer_ids @@ -251,10 +255,12 @@ def queryset(self, request, queryset): # Check for people on holiday if self.value() == "HOLIDAY": - # TODO check if we should move this into a separate filter - customer_ids = Skip.objects.filter( - billing_week=get_billing_week(timezone.now()) - ).only('customer_id').values_list('customer_id') + now = timezone.now() + bw = get_billing_week(now) + billing_week_to_check = effective_billing_week(bw, now) + + customer_ids = customer_ids_on_holiday_for_billing_week( + billing_week_to_check) else: # otherwise @@ -525,6 +531,30 @@ def change_view(self, request, object_id, extra_context=None): return super(CustomerAdmin, self).change_view(request, object_id, extra_context=my_context) + def changelist_view(self, request, extra_context=None): + """ + Overrides the customer change view to let us + extra context for high level stats + """ + + now = timezone.now() + bw = get_billing_week(now) + billing_week_to_check = effective_billing_week(bw, now) + + dashboard_billing_weeks = [ + billing_week_to_check, + billing_week_to_check.next() + ] + + # bww - billing week wednesdays + bww = [fetch_bw_dashboard_stats(dbw) for dbw in dashboard_billing_weeks] + + my_context = { + 'billing_week_wednesdays': bww + } + return super(CustomerAdmin, self).changelist_view(request, + extra_context=my_context) + class CustomerOrderChangeAdmin(BlueWorldModelAdmin): pass @@ -609,6 +639,41 @@ def save_model(self, request, line_item, form, change): admin.site.disable_action('delete_selected') +def fetch_bw_dashboard_stats(billing_week): + """ + Takes a billing week, and returns headline stats about the number + of bags and customers for that week, along with a formatted + date representation. + """ + + active_customer_ids = customer_ids_by_status('ACTIVE') + holidaying_customers = customer_ids_on_holiday_for_billing_week( + billing_week) + + total_bags = 0 + + customers_this_week = Customer.objects.filter( + pk__in=active_customer_ids).exclude( + pk__in=holidaying_customers) + + for c in customers_this_week: + coc = CustomerOrderChange.objects.select_related().filter(customer=c) + cocbq = CustomerOrderChangeBagQuantity.objects.filter( + customer_order_change=coc) + # TODO this is a very expensive query! + for bq in cocbq: + total_bags += bq.quantity + + dbw_stats = { + 'bw': billing_week, + 'date': friendly_date(billing_week.wed), + 'customers': customers_this_week.count(), + 'bags': total_bags + } + + return dbw_stats + + def pickup_list(collection_points, billing_week): ''' Although it would be faster to do all this in the database, in terms of diff --git a/join/helper.py b/join/helper.py index 03c3b98..7b733c5 100644 --- a/join/helper.py +++ b/join/helper.py @@ -1,16 +1,15 @@ from collections import OrderedDict from datetime import timedelta from django.utils import timezone +from django.contrib.humanize.templatetags.humanize import ordinal import datetime import pytz import threading from billing_week import ( - current_billing_week, - get_billing_week, + get_billing_week ) - _thread_locals = threading.local() @@ -73,3 +72,83 @@ def calculate_weekly_fee(bag_quantities): for bag_quantity in bag_quantities: amount += bag_quantity.quantity * bag_quantity.bag_type.weekly_cost return amount + +def friendly_date(date): + """ + Accepts a datetime object and returns a string with ordinalised dates, like + Wednesday 20th July, or Thursday 21st August + """ + + friendly_date = "{} {} {}".format( + date.strftime("%A"), + ordinal(date.strftime("%d")), + date.strftime("%B")) + + return friendly_date + + +def collection_dates_for(bw, collection_point): + """ + Returns a list of zero or more collection dates for a given collection point, + and billing week. + """ + + collection_dates = [] + + if collection_point.collection_day == 'WED': + collection_dates.append(bw.wed) + elif collection_point.collection_day == 'THURS': + collection_dates.append(bw.thurs) + elif collection_point.collection_day == "WED_THURS": + collection_dates.append(bw.wed) + collection_dates.append(bw.thurs) + + return collection_dates + + +def customer_ids_by_status(query_value): + """ + Should this be part of a model manager? + We're having to import inside a method, so probably + """ + from join.models import AccountStatusChange + + ascs = AccountStatusChange.objects.order_by( + 'customer', '-changed' + ).distinct('customer') + + customer_ids = [ + c.customer_id for c in ascs if c.status == query_value + ] + + return customer_ids + +def customer_ids_on_holiday_for_billing_week(billing_week): + """ + Accepts a list of customer ids, and returns the list of ids where + the customer is away for the given billing week + """ + from join.models import Skip + + skipped_customer_ids = Skip.objects.filter( + billing_week=billing_week + ).only('customer_id').values_list('customer_id') + + return skipped_customer_ids + +def effective_billing_week(billing_week, time): + """ + Accepts a billing week, and a date, and returns the 'effective' billing week + based on whether we're before or after the Wednesday in the week. + """ + + wednesday = datetime.datetime( + billing_week.wed.year, + billing_week.wed.month, + billing_week.wed.day, + tzinfo=timezone.get_current_timezone()) + + if time < wednesday: + return billing_week + else: + return billing_week.next() diff --git a/join/migrations/0006_add_instructions_for_collection_point.py b/join/migrations/0006_add_instructions_for_collection_point.py new file mode 100644 index 0000000..6c9a98a --- /dev/null +++ b/join/migrations/0006_add_instructions_for_collection_point.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.8 on 2016-09-01 11:13 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('join', '0005_auto_20160825_1133'), + ] + + operations = [ + migrations.AddField( + model_name='collectionpoint', + name='instructions', + field=models.TextField(blank=True, help_text='If you have specific instructions for accessing the bags, add them here, so users can see them in email or their dashboard', null=True), + ), + ] diff --git a/join/models.py b/join/models.py index 5b61cd0..c5e9df1 100644 --- a/join/models.py +++ b/join/models.py @@ -73,6 +73,14 @@ class CollectionPoint(models.Model): default=FULL, null=True ) + + instructions = models.TextField( + null=True, + blank=True, + help_text=("If you have specific instructions for accessing the bags," + " add them here, so users can see them in email or their dashboard") + ) + WED_THURS = 'WED_THURS' WED = 'WED' THURS = 'THURS' @@ -277,6 +285,20 @@ def _get_latest_account_status(self): return self.account_status_before() account_status = property(_get_latest_account_status) + def _leaving_date(self): + if self.has_left: + leaving_date = AccountStatusChange.objects.order_by( + '-changed' + ).filter(customer=self)[:1][0] + if leaving_date: + return leaving_date.changed.astimezone(london).strftime('%Y-%m-%d %H:%M') + else: + return None + leaving_date = property(_leaving_date) + + def _start_date(self): + return self.created.astimezone(london).strftime('%Y-%m-%d %H:%M') + start_date = property(_start_date) def _has_left(self): @@ -357,6 +379,21 @@ def _get_skipped(self): skipped = property(_get_skipped) + def is_skipped_for_billing_week(self, billing_week): + """ + Returns true or false for a given billing week passed into it + """ + bw_str = str(billing_week) + prev_bw_str = str(billing_week.prev()) + + has_valid_skip = Skip.objects.filter(customer=self, + billing_week=bw_str).all() + + if has_valid_skip: + return True + + return False + def __str__(self): return '{} ({})'.format(self.full_name, self.nickname) diff --git a/join/test_helper.py b/join/test_helper.py new file mode 100644 index 0000000..3d7bcc0 --- /dev/null +++ b/join/test_helper.py @@ -0,0 +1,38 @@ + +from django.test import TestCase +from django.utils import timezone + +from .models import CollectionPoint + +from .helper import collection_dates_for + +from billing_week import get_billing_week + +class TestDashboardHelpers(TestCase): + @classmethod + def setUpTestData(cls): + # Set up data for the whole TestCase + cls.ofs = CollectionPoint.objects.create(name='The Old Fire Station', + collection_day='WED') + cls.mother_earth = CollectionPoint.objects.create(name='Mother Earth', + collection_day='THURS') + cls.black_cat = CollectionPoint.objects.create(name='Black Cat', + collection_day='WED_THURS') + + # we're relying on the billing week class to give us the right dates + cls.bw = get_billing_week(timezone.now()) + + + def test_returns_the_correct_date(self): + self.assertEqual( + collection_dates_for(self.bw, self.ofs), + [self.bw.wed]) + self.assertEqual( + collection_dates_for(self.bw, self.mother_earth), + [self.bw.thurs]) + + + def test_returns_the_correct_date_with_two_collection_days(self): + self.assertEqual( + collection_dates_for(self.bw, self.black_cat), + [self.bw.wed, self.bw.thurs]) diff --git a/join/test_views.py b/join/test_views.py new file mode 100644 index 0000000..5751ad4 --- /dev/null +++ b/join/test_views.py @@ -0,0 +1,84 @@ + +from decimal import Decimal + +from django.test import TestCase +from django.test import Client + + +from django.utils import timezone + +from allauth.utils import get_user_model, get_current_site +# we want two collection points + +from .models import CollectionPoint + +# we also want at least one user +from .models import (CollectionPoint, CustomerOrderChangeBagQuantity, + CustomerOrderChange, Customer, AccountStatusChange, BagType, CollectionPoint) + + +from billing_week import get_billing_week + + + + +class TestCollectionsShowsRightBillingWeeks(TestCase): + + @classmethod + def setUpTestData(self): + + + self.user = get_user_model().objects.create(username="joe", email="joe@example.com", password="sekrit") + + now = timezone.now() + bw = get_billing_week(now) + + self.joe = Customer( + created=now, + created_in_billing_week=str(bw), + full_name="Joseph Bloggs", + nickname="Joe", + user=self.user + ) + + self.joe.save() + + small_bags = BagType.objects.create(name="Med Veg", active=True) + small_bags.weekly_cost = Decimal('9.00') + small_bags.save() + + ofs = CollectionPoint.objects.create( + location='61 Leswin Road, N16 7NX', + collection_day='WED', + active=True, + name='The Old Fire Station') + + ofs.save() + + self.joe.collection_point = ofs + self.joe._set_bag_quantities({small_bags.id: 1}) + + account_status_change = AccountStatusChange( + changed=now, + changed_in_billing_week=str(bw), + customer=self.joe, + status=AccountStatusChange.ACTIVE, + ).save() + + + + + def test_dates_of_collections(self): + + response = self.client.post('/login', { 'login':'joe@example.com', 'password':'sekrit'}) + + + + # Check that the response is 200 OK. + self.assertEqual(response.status_code, 200) + # import ptpdb; ptpdb.set_trace() + + # Check that the rendered context contains 5 customers. + # self.assertEqual(len(response.context['customers']), 5) + # + # diff --git a/join/views.py b/join/views.py index 2435529..8cdcebf 100644 --- a/join/views.py +++ b/join/views.py @@ -11,7 +11,9 @@ import uuid from allauth.account.views import signup as allauth_signup -from billing_week import get_billing_week, parse_billing_week, billing_weeks_left_in_the_month +from billing_week import ( + get_billing_week, parse_billing_week, billing_weeks_left_in_the_month, + next_valid_billing_week, next_n_billing_weeks) from django import forms from django.conf import settings from django.contrib import messages @@ -33,7 +35,12 @@ import pytz from billing_week import first_wed_of_month as start_of_the_month -from join.helper import get_pickup_dates, render_bag_quantities, calculate_weekly_fee +from join.helper import ( + get_pickup_dates, + friendly_date, + render_bag_quantities, + calculate_weekly_fee, + collection_dates_for) from .models import ( AccountStatusChange, BagType, @@ -513,6 +520,8 @@ def collection_point(request): locations['id_collection_point_{}'.format(i)] = { "longitude": escape(collection_point.longitude), "latitude": escape(collection_point.latitude), + "name": escape(collection_point.name), + "location": escape(collection_point.location), } return render( request, @@ -699,120 +708,90 @@ def gocardless_callback(request): @gocardless_is_set_up def dashboard(request): if request.user.customer.account_status != AccountStatusChange.LEFT: - latest_cp_change = CustomerCollectionPointChange.objects.order_by( + + now = timezone.now() + bw = get_billing_week(now) + + + + # we want a list of billing weeks, so we can then pull out changes for them + next_bws = next_n_billing_weeks(3, bw) + + # billing weeks starting Sunday 3pm should reflect the latest change made this week + + # the order in the current billing week should only reflect the last change made in the week before + + last_used_ccpc = CustomerCollectionPointChange.objects.order_by( '-changed' - ).filter(customer=request.user.customer)[:1] - latest_customer_order_change = CustomerOrderChange.objects.order_by( + ).filter(customer=request.user.customer, changed_in_billing_week__lt=bw)[:1] + + latest_ccpc = CustomerCollectionPointChange.objects.order_by( + '-changed' + ).filter(customer=request.user.customer)[:1][0] + + latest_collection_point = latest_ccpc.collection_point + + last_used_order = CustomerOrderChange.objects.order_by( + '-changed' + ).filter(customer=request.user.customer, changed_in_billing_week__lt=bw)[:1] + + # if someone changes their order this billing week, we capture this, + # so we can show it applying to future orders + latest_order = CustomerOrderChange.objects.order_by( '-changed' ).filter(customer=request.user.customer)[:1] - collection_point = latest_cp_change[0].collection_point - now = timezone.now() - bw = get_billing_week(now) - weekday = now.weekday() - skipped_billing_weeks = [] - skipped = len( - Skip.objects.order_by('billing_week').filter( - customer=request.user.customer, - billing_week=str(bw), - ).all() - ) > 0 - - if weekday == 6: # Sunday - if collection_point.collection_day == 'WED': - collection_date = 'Wednesday' - elif collection_point.collection_day == 'THURS': - collection_date = 'Thursday' - else: - collection_date = 'Wednesday and Thursday' - if timezone.now().hour < bw.end.hour: - deadline = '3pm today' - changes_affect = "next week's collection" - else: - deadline = '3pm next Sunday' - changes_affect = "the collection after next" - elif weekday == 0: # Monday - if collection_point.collection_day == 'WED': - collection_date = 'Wednesday' - elif collection_point.collection_day == 'THURS': - collection_date = 'Thursday' - else: - collection_date = 'Wednesday and Thursday' - deadline = '3pm this Sunday' - changes_affect = "next week's collection" - elif weekday == 1: # Tuesday - if collection_point.collection_day == 'WED': - collection_date = 'tomorrow' - elif collection_point.collection_day == 'THURS': - collection_date = 'Thursday' - else: - collection_date = 'tomorrow and Thursday' - deadline = '3pm this Sunday' - changes_affect = "next week's collection" - elif weekday == 2: # Wednesday - if collection_point.collection_day == 'WED': - collection_date = 'today' - elif collection_point.collection_day == 'THURS': - collection_date = 'tomorrow' - else: - collection_date = 'today and tomorrow' - deadline = '3pm this Sunday' - changes_affect = "next week's collection" - elif weekday == 3: # Thurs - if collection_point.collection_day == 'WED': - collection_date = 'Wednesday next week' - elif collection_point.collection_day == 'THURS': - collection_date = 'today' - else: - collection_date = 'today' - deadline = '3pm this Sunday' - changes_affect = "next week's collection" - else: - if collection_point.collection_day == 'WED': - collection_date = 'Wednesday next week' - elif collection_point.collection_day == 'THURS': - collection_date = 'Thursday next week' - else: - collection_date = 'Wednesday and Thursday next week' - if weekday == 4: # Friday - deadline = '3pm this Sunday' - else: # Saturday - deadline = '3pm tomorrow' - changes_affect = "next week's collection" - - # fall back for the case when we have a user just starting this week - if request.user.customer.created_in_billing_week == bw: - if collection_point.collection_day == 'WED': - collection_date = 'Wednesday next week' - elif collection_point.collection_day == 'THURS': - collection_date = 'Thursday next week' - else: - collection_date = 'Wednesday and Thursday next week' - if weekday == 4: # Friday - deadline = '3pm this Sunday' - else: # Saturday - deadline = '3pm tomorrow' - changes_affect = "next week's collection" - - bag_quantities = CustomerOrderChangeBagQuantity.objects.filter( - customer_order_change=latest_customer_order_change + + last_used_bag_quantities = CustomerOrderChangeBagQuantity.objects.filter( + customer_order_change=last_used_order + ).all() + + latest_bag_quantities = CustomerOrderChangeBagQuantity.objects.filter( + customer_order_change=latest_order ).all() - if skipped: - collection_date = collection_date.replace(' and ', ' or ') - if not ( - collection_date.startswith('today') or - collection_date.startswith('tomorrow') - ): - collection_date = 'on '+ collection_date + + # if we don't have these, the the user hasn't been in + if not last_used_ccpc or not last_used_order: + new_customer = True + collections = [{ + 'billing_week': bw, + 'collection_point': latest_collection_point, + 'dates': [friendly_date(d) for d in collection_dates_for(bw, latest_collection_point)], + 'bags': latest_bag_quantities, + }] + else: + new_customer = False + last_used_collection_point = last_used_ccpc[0].collection_point + + # the billing week object, the dates, and the order + collections = [{ + 'billing_week': bw, + 'collection_point': last_used_collection_point, + 'dates': [friendly_date(d) for d in collection_dates_for(bw, last_used_collection_point)], + 'bags': last_used_bag_quantities, + 'skipped': request.user.customer.is_skipped_for_billing_week(bw) + }] + + for bwk in next_bws: + collections.append({ + 'billing_week': bwk, + 'collection_point': latest_collection_point, + 'dates': [friendly_date(d) for d in collection_dates_for(bwk, latest_collection_point)], + 'bags': latest_bag_quantities, + 'skipped': request.user.customer.is_skipped_for_billing_week(bwk) + }) + return render( request, 'dashboard/index.html', { - 'bag_quantities': bag_quantities, - 'collection_point': collection_point, - 'collection_date': collection_date, - 'deadline': deadline, - 'changes_affect': changes_affect, - 'skipped': skipped, + 'new_customer': new_customer, + 'this_bw': bw, + 'collections': collections, + 'now': now, + 'next_bws': next_bws, + 'skips': "skips" + # 'deadline': deadline, + # 'changes_affect': changes_affect, } ) else: @@ -973,6 +952,8 @@ def dashboard_change_collection_point(request): locations['id_collection_point_{}'.format(i)] = { "longitude": escape(collection_point.longitude), "latitude": escape(collection_point.latitude), + "name": escape(collection_point.name), + "location": escape(collection_point.location), } return render( request, @@ -1413,3 +1394,27 @@ def billing_dates(request): 'current_billing_week': bw_today } ) + +from billing_week import get_billing_week +from join.admin import pickup_list + +def generate_pickup_list(request, year=None, month=None, day=None): + + now = timezone.now() + + if year and month and day: + time = datetime.datetime(int(year), int(month), int(day), + tzinfo=timezone.get_current_timezone()) + billing_week_time = time + else: + billing_week_time = now + + bw = get_billing_week(billing_week_time) + queryset = CollectionPoint.objects.all() + context = pickup_list( + queryset, + bw + ) + context['now_billing_week'] = bw + context['now'] = now + return render(request, 'pickup-list.html', context)