diff --git a/souschef/frontend/js/meal-cancellation.js b/souschef/frontend/js/meal-cancellation.js new file mode 100644 index 00000000..c184faec --- /dev/null +++ b/souschef/frontend/js/meal-cancellation.js @@ -0,0 +1,21 @@ +$(function() { + + // Javascript of the meal cancellation. + // ************************************** + + var config = { + dateFormat: "yy-mm-dd", + separator: "|" + }; + + $('#id_cancel_meal_dates').hide(); + $('#cancel_meal_dates').multiDatesPicker(config); + if($('#id_cancel_meal_dates').val()){ + $('#cancel_meal_dates').multiDatesPicker('addDates', JSON.parse($('#id_cancel_meal_dates').val())); + + } + $('#cancel_meal_dates').bind('DOMSubtreeModified', function(){ + var dates = $('#cancel_meal_dates').multiDatesPicker('getDates'); + $('#id_cancel_meal_dates').val(JSON.stringify(dates)); + }); +}); \ No newline at end of file diff --git a/souschef/member/forms.py b/souschef/member/forms.py index 5be202ea..e7a92b1c 100644 --- a/souschef/member/forms.py +++ b/souschef/member/forms.py @@ -1,3 +1,6 @@ +from datetime import datetime +import json + from django import forms from django.core.exceptions import ObjectDoesNotExist from django.utils import timezone @@ -245,6 +248,11 @@ def __init__(self, *args, **kwargs): ) ) + cancel_meal_dates = forms.CharField( + required=False, + label=_('Cancel dates'), + ) + def clean(self): """ The meal defaults are required for the scheduled delivery days: @@ -256,6 +264,16 @@ def clean(self): """ super(ClientRestrictionsInformation, self).clean() + def to_cancel_date(str_date): + return datetime.strptime( + str_date, "%Y-%m-%d" + ).date() + + if self.cleaned_data.get('cancel_meal_dates'): + self.cleaned_data['cancel_meal_dates'] = list(map(to_cancel_date, + json.loads(self.cleaned_data['cancel_meal_dates']))) + + if self.cleaned_data.get('delivery_type') == 'O': # Ongoing meals_schedule = self.cleaned_data.get('meals_schedule') diff --git a/souschef/member/migrations/0035_client_cancel_meal_date.py b/souschef/member/migrations/0035_client_cancel_meal_date.py new file mode 100644 index 00000000..36d220cd --- /dev/null +++ b/souschef/member/migrations/0035_client_cancel_meal_date.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.29 on 2021-01-30 18:08 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('member', '0034_auto_20170816_0850'), + ] + + operations = [ + migrations.CreateModel( + name='Client_cancel_meal_date', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('cancel_date', models.DateField(default=django.utils.timezone.now)), + ('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cancel_meal_dates', to='member.Client', verbose_name='client')), + ], + ), + ] diff --git a/souschef/member/models.py b/souschef/member/models.py index d6cf28d1..e1ce35c2 100644 --- a/souschef/member/models.py +++ b/souschef/member/models.py @@ -1105,3 +1105,25 @@ def __str__(self): return "{} {} {}".format(self.client.member.firstname, self.client.member.lastname, self.component.name) + + +class Client_cancel_meal_date(models.Model): + client = models.ForeignKey( + 'member.Client', + verbose_name=_('client'), + on_delete=models.CASCADE, + related_name='cancel_meal_dates' + ) + + cancel_date = models.DateField( + auto_now=False, + auto_now_add=False, + default=timezone.now, + null=False + ) + + + def __str__(self): + return "{} {} {}".format(self.client.member.firstname, + self.client.member.lastname, + self.cancel_date) \ No newline at end of file diff --git a/souschef/member/templates/client/partials/forms/dietary_restriction.html b/souschef/member/templates/client/partials/forms/dietary_restriction.html index dfa3f851..1e7a16dc 100644 --- a/souschef/member/templates/client/partials/forms/dietary_restriction.html +++ b/souschef/member/templates/client/partials/forms/dietary_restriction.html @@ -1,4 +1,5 @@ {% load i18n %} +{% load static %}

{% trans 'Meal status' %}

@@ -54,3 +55,9 @@

{% trans 'Food preferences' %}

{{ form.dish_to_avoid }}
+ +

{% trans 'Meal cancelation' %}

+
+
+ {{ form.cancel_meal_dates }} +
\ No newline at end of file diff --git a/souschef/member/templates/client/update/base.html b/souschef/member/templates/client/update/base.html index 30165c97..3a2254f3 100644 --- a/souschef/member/templates/client/update/base.html +++ b/souschef/member/templates/client/update/base.html @@ -8,6 +8,9 @@ {% load leaflet_tags %} {% leaflet_js %} {% leaflet_css %} + + {% comment %} Wash out jquery UI css on other semantic UI elements by importing it again. {% endcomment %} + {% endblock %} {% block title %} @@ -76,4 +79,14 @@

{% else %} {% endif %} + + + + {% if debug %} + + {% else %} + + {% endif %} {% endblock %} diff --git a/souschef/member/views.py b/souschef/member/views.py index 19ad0b8a..70a992f7 100644 --- a/souschef/member/views.py +++ b/souschef/member/views.py @@ -1,6 +1,9 @@ # coding: utf-8 import csv +import json + +from datetime import datetime from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin @@ -45,6 +48,7 @@ DAYS_OF_WEEK, Client_avoid_ingredient, Client_avoid_component, + Client_cancel_meal_date, HOME, WORK, CELL, EMAIL) from souschef.order.mixins import FormValidAjaxableResponseMixin from souschef.order.models import SIZE_CHOICES, Order @@ -224,6 +228,7 @@ def save(self, id=None): 'payment_information'].cleaned_data dietary_restriction = self.form_dict[ 'dietary_restriction'].cleaned_data + member, created = Member.objects.update_or_create( id=id, @@ -407,7 +412,8 @@ def save_preferences(self, client): Client_avoid_component.objects.create( client=client, component=component_to_avoid - ) + ) + def billing_member_is_member(self): basic_information = self.form_dict['basic_information'] @@ -896,6 +902,11 @@ def get_initial(self): client = get_object_or_404( Client, pk=self.kwargs.get('pk') ) + cancel_meal_dates = [datetime.combine(date_entry.cancel_date, datetime.min.time()).strftime("%Y-%m-%d") + for date_entry + in client.cancel_meal_dates.all()] + cancel_meal_dates_json = json.dumps(cancel_meal_dates) + initial.update({ 'status': True if client.status == Client.ACTIVE else False, 'delivery_type': client.delivery_type, @@ -904,6 +915,7 @@ def get_initial(self): 'ingredient_to_avoid': client.ingredients_to_avoid.all, 'dish_to_avoid': client.components_to_avoid.all, 'food_preparation': client.food_preparation.all, + 'cancel_meal_dates': cancel_meal_dates_json, }) for k, v in (client.meal_default_week or {}).items(): initial[k] = v @@ -953,6 +965,15 @@ def save(self, form, client): component=component_to_avoid ) + # Save cancel meal dates + client.cancel_meal_dates.all().delete() + cancel_meal_dates = form['cancel_meal_dates'] + for cancel_meal_date in cancel_meal_dates: + Client_cancel_meal_date.objects.create( + client=client, + cancel_date=cancel_meal_date + ) + # Save preferences json = {} for days, v in DAYS_OF_WEEK: diff --git a/souschef/order/models.py b/souschef/order/models.py index b2f2a67c..4e0fd3e7 100644 --- a/souschef/order/models.py +++ b/souschef/order/models.py @@ -158,6 +158,9 @@ def auto_create_orders(self, delivery_date, clients): weekday = delivery_date.weekday() # Monday is 0, Sunday is 6 day = DAYS_OF_WEEK[weekday][0] # 0 -> 'monday', 6 -> 'sunday' for client in clients: + is_cancel = client.cancel_meal_dates.filter(cancel_date=delivery_date) + if is_cancel: + continue try: order = Order.objects.get(client=client, delivery_date=delivery_date) diff --git a/tools/gulp/gulpfile.js b/tools/gulp/gulpfile.js index ad64a7d6..97318935 100644 --- a/tools/gulp/gulpfile.js +++ b/tools/gulp/gulpfile.js @@ -65,6 +65,14 @@ const sources = { site: [ `${SRC_JS}/multidatespicker.js`, ] + }, + mealCancellation: { + vendor: [ + 'node_modules/jquery-ui-multi-date-picker/dist/jquery-ui.multidatespicker.js', + ], + site: [ + `${SRC_JS}/meal-cancellation.js`, + ] } }, @@ -161,6 +169,17 @@ gulp.task('scripts-multidatespicker', () => .pipe(gulp.dest(destinations.js)) ); +gulp.task('scripts-meal-cancellation', () => + gulp.src([].concat(sources.js.mealCancellation.vendor).concat(sources.js.mealCancellation.site)) + .pipe(concat('meal-cancellation.js')) + .pipe(gulp.dest(destinations.js)) + .pipe(bytediff.start()) + .pipe(uglify()) + .pipe(bytediff.stop(bytediffFormatter)) + .pipe(rename({ suffix: '.min' })) + .pipe(gulp.dest(destinations.js)) +); + gulp.task('images', () => gulp.src([].concat(sources.img.vendor).concat(sources.img.site)) .pipe(cache(imagemin({ @@ -177,13 +196,13 @@ gulp.task('fonts', () => ); gulp.task('default', () => { - gulp.start('styles', 'scripts-multidatespicker', 'scripts-leaflet', 'scripts', + gulp.start('styles', 'scripts-multidatespicker', 'scripts-leaflet', 'scripts', 'scripts-meal-cancellation', 'images', 'fonts'); }); gulp.task('watch', ['default'], () => { gulp.watch(`${SRC_SCSS}/**/*.scss`, ['styles']); - gulp.watch(`${SRC_JS}/**/*.js`, ['scripts-multidatespicker', 'scripts-leaflet', 'scripts']); + gulp.watch(`${SRC_JS}/**/*.js`, ['scripts-multidatespicker', 'scripts-leaflet', 'scripts', 'scripts-meal-cancellation']); gulp.watch(`${SRC_IMG}/**/*`, ['images']); }); @@ -192,6 +211,7 @@ gulp.task('validate', () => .concat(sources.js.scripts.site) .concat(sources.js.leaflet.site) .concat(sources.js.multiDatesPicker.site) + .concat(sources.js.mealCancellation.site) ) .pipe(debug()) .pipe(validate())