diff --git a/src/accounts/forms.py b/src/accounts/forms.py index d457507..01f9d83 100644 --- a/src/accounts/forms.py +++ b/src/accounts/forms.py @@ -79,9 +79,13 @@ class Meta: "work_phone", "birth_date", "join_date", + "active", + "end_date", + "end_reason", "notes", ] widgets = { "birth_date": DateInput(format=("%Y-%m-%d")), "join_date": DateInput(format=("%Y-%m-%d")), + "end_date": DateInput(format=("%Y-%m-%d")), } diff --git a/src/models/management/commands/seed.py b/src/models/management/commands/seed.py new file mode 100644 index 0000000..d5f38d4 --- /dev/null +++ b/src/models/management/commands/seed.py @@ -0,0 +1,120 @@ +from django.core.management.base import BaseCommand +import random +from address.models import Address, Locality, State, Country + +from logging import getLogger + +from models.models import ( + Customer, + CustomerRecord, + Payment +) + + +log = getLogger(__name__) + +# python manage.py seed --mode=refresh + +""" Clear all data and creates cusotmers """ +MODE_REFRESH = 'refresh' + +""" Clear all data and do not create any object """ +MODE_CLEAR = 'clear' + +class Command(BaseCommand): + help = "seed database for testing and development." + + def add_arguments(self, parser): + parser.add_argument('--mode', type=str, help="Mode") + + def handle(self, *args, **options): + self.stdout.write('seeding data...') + run_seed(self, options['mode']) + self.stdout.write('done.') + + +def clear_data(): + """Deletes all the table data""" + log.info("Deleting customers") + Customer.objects.all().delete() + CustomerRecord.objects.all().delete() + + +def create_customer(local, payment_types): + """Creates an customer object combining different elements from the list""" + log.info("Creating customer") + + active_options = [True, False] + first_name_options = ["Bob", "Jane", "Joe", "Hank", "Jill", "Tom", "Alice"] + last_name_options = ["Smith", "Adams", "Thompson", "Berry", "Phillips"] + + + a = Address( + street_number="1", + route="Some Street", + locality=local, + raw="1 Some Street, Charlottesvile, VA" + ) + a.save() + + c = Customer( + active=random.choice(active_options), + first_name=random.choice(first_name_options), + last_name=random.choice(last_name_options), + address=a + ) + c.save() + + + cr = CustomerRecord( + customer=c, + num_meals=1, + payment_type=random.choice(payment_types) + ) + cr.save() + + log.info("{} customer created.".format(c)) + return c + +def run_seed(self, mode): + """ Seed database based on mode + + :param mode: refresh / clear + :return: + """ + # Clear data from tables + clear_data() + if mode == MODE_CLEAR: + return + + payment_types = [] + + try: + country = Country(name="US") + country.save() + state = State(name="VA", country=country) + state.save() + local = Locality(name="Charlottesville", postal_code="22911", state=state) + local.save() + except: + local = Locality.objects.first() + + try: + p1 = Payment(name="debit") + p1.save() + p2 = Payment(name="credit") + p2.save() + p3 = Payment(name="cash") + p3.save() + + payment_types = [p1,p2,p3] + except: + payment_types = Payment.objects.all() + + + # Creating 30 customers + for i in range(30): + try: + create_customer(local,payment_types) + except: + pass \ No newline at end of file diff --git a/src/models/migrations/0034_volunteer_end_date_volunteer_end_reason.py b/src/models/migrations/0034_volunteer_end_date_volunteer_end_reason.py new file mode 100644 index 0000000..7a6e485 --- /dev/null +++ b/src/models/migrations/0034_volunteer_end_date_volunteer_end_reason.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0.4 on 2024-08-11 00:44 + +from django.db import migrations, models + + +def set_default_values(apps, schema_editor): + Volunteer = apps.get_model('models', 'volunteer') + db_alias = schema_editor.connection.alias + + Volunteer.objects.update(active=True) + +class Migration(migrations.Migration): + + dependencies = [ + ('models', '0033_alter_route_bonusroute'), + ] + + operations = [ + migrations.AddField( + model_name='volunteer', + name='end_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='volunteer', + name='end_reason', + field=models.TextField(blank=True, default=''), + ), + migrations.AddField( + model_name='volunteer', + name='active', + field=models.BooleanField(default=True), + ), + migrations.RunPython(set_default_values), + ] diff --git a/src/models/models.py b/src/models/models.py index f4519e5..8d60d7c 100644 --- a/src/models/models.py +++ b/src/models/models.py @@ -93,6 +93,9 @@ class Volunteer(models.Model): birth_date = models.DateField(null=True, blank=True) notes = models.TextField(default="", blank=True) join_date = models.DateField(default=date.today) + end_date=models.DateField(null=True, blank=True) + end_reason = models.TextField(default="", blank=True) + active = models.BooleanField(default=True) number_of_people = models.IntegerField(default=1) dont_email = models.BooleanField(default=False) diff --git a/src/pdfs/templates/pdfs/job-overview.html b/src/pdfs/templates/pdfs/job-overview.html index d11906c..9829ac6 100644 --- a/src/pdfs/templates/pdfs/job-overview.html +++ b/src/pdfs/templates/pdfs/job-overview.html @@ -2,20 +2,22 @@

MOW Job Overview Report

Generated: {{today|date:'l, M. d, Y'}} at {{today|time:'h:i a'}}
** denotes volunteering via substitution
-{%for d in dates%} - {% for jt in d.job_types %} -

{{jt.job_type.name}} ({{d.date|date:'l, M. d, Y'}})

+{%for c in context%} + {% for jt in c.job_types %} +

{{jt.job_type_name}} ({{c.date|date:'l, M. d, Y'}})

+ + {% for j in jt.jobs %} - + + + {% empty %} diff --git a/src/pdfs/templates/pdfs/monthly-billing.html b/src/pdfs/templates/pdfs/monthly-billing.html index fc73759..167098d 100644 --- a/src/pdfs/templates/pdfs/monthly-billing.html +++ b/src/pdfs/templates/pdfs/monthly-billing.html @@ -56,9 +56,9 @@

{{ payment_type|getPayment }}:

- {% for reportday in reportday_section %} + {% for reportday in reportday_section|sort_name:"customer__last_name,customer__first_name" %} - + diff --git a/src/pdfs/templatetags/route_extras.py b/src/pdfs/templatetags/route_extras.py index 0da4a60..3c4dfc3 100644 --- a/src/pdfs/templatetags/route_extras.py +++ b/src/pdfs/templatetags/route_extras.py @@ -89,3 +89,8 @@ def numMealsOnDay(cust, date): register.filter("getItem", getItem) register.filter("numMealsOnDay", numMealsOnDay) register.filter("getRange", getRange) + +@register.filter +def sort_name(val, args): + x, y = args.split(',') + return sorted(val, key=lambda item: (item[x].lower(), item[y].lower())) diff --git a/src/pdfs/views.py b/src/pdfs/views.py index 285e8a0..b3165a6 100644 --- a/src/pdfs/views.py +++ b/src/pdfs/views.py @@ -39,19 +39,21 @@ "Delivery Status", "Client Status", "Notes", "Volunteer"] -def to_pdf(html, styleSheetPath=None): +def to_pdf(html, styleSheetPath=None, options=None): global _display if not _display: _display = Display(visible=0, size=(320, 240)).start() - options = { - "page-size": "Letter", - "margin-top": "0.74in", - "margin-right": "0.39in", - "margin-bottom": "0.39in", - "margin-left": "0.39in", - "print-media-type": True, - "user-style-sheet": styleSheetPath - } + if not options: + options = { + "page-size": "Letter", + "margin-top": "0.74in", + "margin-right": "0.39in", + "margin-bottom": "0.39in", + "margin-left": "0.39in", + "footer-right": "[page] of [topage]", + "print-media-type": True, + "user-style-sheet": styleSheetPath + } pdf = pdfkit.from_string(html, False, options) @@ -212,13 +214,33 @@ def job_overview_report(request, begin_date, end_date): begin_date_datetime = datetime.datetime.strptime(begin_date, "%m-%d-%Y") end_date_datetime = datetime.datetime.strptime(end_date, "%m-%d-%Y") dates = actuals_to_display( - begin_date_datetime, end_date_datetime + datetime.timedelta(days=1) + begin_date_datetime, end_date_datetime + datetime.timedelta(days=1), + skip_unfilled_end=True ) + + context = [] + for d in dates: + job_types = [] + for jt in d.job_types: + details = {"job_type_name": jt.job_type.name} + jobs = [] + for job in jt.jobs: + total_num_meals = 0 + customers = Customer.objects.filter(route=job.route_number) + for c in customers: + num_meals = c.num_meals_on_day(d.date) + total_num_meals += num_meals + jobs.append({"job": job, "number_of_customers": len(customers), "total_meals": total_num_meals}) + details["jobs"] = jobs + + job_types.append(details) + context.append({"date": d.date, "job_types": job_types}) + template = get_template("pdfs/job-overview.html") return to_pdf( template.render( { - "dates": dates, + "context": context, "open_substitution": OPEN_SUBSTITUTION, "unassigned_job": UNASSIGNED_JOB, } @@ -336,8 +358,10 @@ def monthly_billing_report(request, begin_date, end_date): CustomerRecord.objects.filter( num_meals__gte=1, date__gte=begin_date, - date__lte=end_date) .values( - "customer", + date__lte=end_date).select_related('customer').values( + "customer__id", + "customer__first_name", + "customer__last_name", "payment_type", "route_assigned") .annotate( total_meals=Sum("num_meals")) .order_by("payment_type")) @@ -576,6 +600,10 @@ def generate_routes_report(request, date): for c in custs: total_meals += c.num_meals_on_day(date) + + if total_meals < 1: + continue + # Append to data data.append({"route": route, "customer_list": custs, "total_meals": total_meals, "date": date, "vols": vols}) @@ -583,4 +611,12 @@ def generate_routes_report(request, date): template = get_template("pdfs/generate-routes-report.html") context = {"data": data} - return to_pdf(template.render(context), '/collected-static/pdfs/route_all_pdf.css') + return to_pdf(template.render(context), options = { + "page-size": "Letter", + "margin-top": "0.74in", + "margin-right": "0.39in", + "margin-bottom": "0.39in", + "margin-left": "0.39in", + "footer-right": "[page] of [topage]", + "user-style-sheet": '/collected-static/pdfs/route_all_pdf.css' + }) diff --git a/src/requirements.txt b/src/requirements.txt index 620e02f..0f2836e 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -32,7 +32,6 @@ graphviz==0.20 gunicorn==20.1.0 idna==3.3 inflection==0.5.1 -install==1.3.5 isort==5.10.1 itypes==1.2.0 Jinja2==3.1.1 diff --git a/src/staff/templates/edit-volunteer.html b/src/staff/templates/edit-volunteer.html index 860f5c4..e05f54c 100644 --- a/src/staff/templates/edit-volunteer.html +++ b/src/staff/templates/edit-volunteer.html @@ -92,6 +92,17 @@

Edit Volunteer

{{ form.join_date|as_crispy_field }} +
+
+ {{ form.end_date|as_crispy_field }} +
+
+ {{ form.active|as_crispy_field }} +
+
+ {{ form.end_reason|as_crispy_field }} +
+
{{ form.notes|as_crispy_field }} diff --git a/src/staff/views/customer_management.py b/src/staff/views/customer_management.py index 48ff0eb..0a587b9 100644 --- a/src/staff/views/customer_management.py +++ b/src/staff/views/customer_management.py @@ -71,7 +71,7 @@ def manage_customers(request): will bring up all custome """ # get all the substitutions in the DB from today on - all_customers = Customer.objects.all().order_by("first_name", "last_name") + all_customers = Customer.objects.all().order_by("last_name", "first_name") return render(request, "manage-customers.html", {"cstmrs": all_customers}) @@ -152,7 +152,7 @@ def export_customers(request): writer = csv.writer(response) writer.writerow(field_names) - customers = Customer.objects.filter(active=1) + customers = Customer.objects.all().order_by("last_name", "first_name") for obj in customers: writer.writerow([getattr(obj, field) for field in field_names]) diff --git a/src/staff/views/job_management.py b/src/staff/views/job_management.py index 515fa59..312917e 100644 --- a/src/staff/views/job_management.py +++ b/src/staff/views/job_management.py @@ -134,6 +134,7 @@ def actuals_to_display( original=NO_FILTER_SENTINAL, job=NO_FILTER_SENTINAL, exclude_unfilled=False, + skip_unfilled_end=False, **kwargs, # support future changes to actuals() interface ): actuals = Assignment.actuals( @@ -186,7 +187,7 @@ def actuals_to_display( dates[dIndex].emails.add(a.volunteer.user.email) # insert unfilled jobs to the end - if "a" in locals(): + if "a" in locals() and not skip_unfilled_end: for ji in range(jobs_index, len(all_jobs)): job_struct = populate_structs(dates, a.date, all_jobs[ji]) diff --git a/src/staff/views/volunteer_management.py b/src/staff/views/volunteer_management.py index e6373c1..5e14680 100644 --- a/src/staff/views/volunteer_management.py +++ b/src/staff/views/volunteer_management.py @@ -17,10 +17,12 @@ from staff.forms import VolunteerUserForm, AddressForm from staff.views.email import send_email, reset_password + import csv import datetime import time + log = getLogger(__name__) # Signup is not an option available in auth so we have to create our own @@ -51,10 +53,10 @@ def create_volunteer(request): # volunteer vol = Volunteer.objects.get(user=user) vol.address = address - vol.save() other_vol_form = VolunteerForm(request.POST, instance=vol) - other_vol_form.save() - + vol = other_vol_form.save(commit=False) + vol.active = True + vol.save() # send password reset email reset_password(email, request) @@ -62,7 +64,7 @@ def create_volunteer(request): else: user_form = SignUpForm() - vol_form = VolunteerForm() + vol_form = VolunteerForm(initial={'active':True}) addr_form = AddressForm() context = { "user_form": user_form, @@ -179,15 +181,28 @@ def export_volunteers(request): filename = "volunteer-export-" + today + ".csv" response["Content-Disposition"] = 'attachment; filename="' + filename + '"' - blank_volunteer = Volunteer() - field_names = [field.name for field in blank_volunteer._meta.fields] + user_fields = ["email", "first_name", "last_name"] + volunteer_fields = ["id", "active", "organization","address","home_phone","cell_phone","work_phone","birth_date","notes","join_date","number_of_people","dont_email", "end_date", "end_reason"] writer = csv.writer(response) - writer.writerow(field_names) + writer.writerow(user_fields + volunteer_fields + ["last_job"]) volunteers = Volunteer.objects.all() + for obj in volunteers: - writer.writerow([getattr(obj, field) for field in field_names]) + last_job = None + record = VolunteerRecord.objects.filter(volunteer=obj).last() + if record is not None: + last_job = record.date + # get associated user fields + data = [] + for field in user_fields: + data.append(getattr(obj.user, field)) + for field in volunteer_fields: + data.append(getattr(obj, field)) + + data.append(last_job) + writer.writerow(data) return response
Job Name VolunteersClientsMeals
{{j.job.name}}{{j.job.job.name}} - {% for person in j.todays_volunteers %} + {% for person in j.job.todays_volunteers %} {% if person.sub_pk %} {% if person.name == open_substitution %} {{open_substitution}}{% if not forloop.last %},{% endif %} @@ -30,6 +32,8 @@

{{jt.job_type.name}} ({{d.date|date:'l, M. d, Y'} {{unassigned_job}} {% endfor%}

{{j.number_of_customers}}{{j.total_meals}}
Nothing to display.Route
{{ reportday.customer|getCustomer }}{{ reportday.customer__first_name }} {{ reportday.customer__last_name }} {{ reportday.total_meals }} {{ reportday.payment_type|getPayment }} {{ reportday.route_assigned|getRoute }}