From 33a6e5cb02189b7621a279c32c12b5c3d83ba680 Mon Sep 17 00:00:00 2001 From: Mitch Riedstra Date: Thu, 9 Nov 2017 15:57:46 -0500 Subject: Show number of attachments, and color code issues with loads. Prevent invoice generation on zero amounts and no attachments. Add a way for superusers to edit invoices. Add a field to invoices for a payment identifier. E.g. "Check #1234" --- app/dispatch/admin.py | 3 +- app/dispatch/migrations/0001_initial.py | 2 +- app/dispatch/migrations/0002_auto_20171102_1949.py | 65 ---------------------- app/dispatch/migrations/0002_auto_20171107_1912.py | 65 ++++++++++++++++++++++ .../migrations/0003_invoice_payment_identifer.py | 20 +++++++ app/dispatch/models.py | 8 +++ app/dispatch/monkey_patch.py | 10 ---- .../templates/dispatch/drivers/detail.html | 14 ++++- .../templates/dispatch/drivers/summary.html | 47 ++++++++++------ .../templates/dispatch/generic_load_listing.html | 18 +++++- .../templates/dispatch/invoice/detail-table.html | 22 ++++++++ .../templates/dispatch/invoice/detail.html | 8 ++- app/dispatch/templates/dispatch/invoice/edit.html | 21 +++++++ app/dispatch/templates/dispatch/loads/list.html | 2 +- app/dispatch/urls.py | 2 +- app/dispatch/views.py | 64 +++++++++++++++++++-- 16 files changed, 266 insertions(+), 105 deletions(-) delete mode 100644 app/dispatch/migrations/0002_auto_20171102_1949.py create mode 100644 app/dispatch/migrations/0002_auto_20171107_1912.py create mode 100644 app/dispatch/migrations/0003_invoice_payment_identifer.py delete mode 100644 app/dispatch/monkey_patch.py create mode 100644 app/dispatch/templates/dispatch/invoice/edit.html (limited to 'app/dispatch') diff --git a/app/dispatch/admin.py b/app/dispatch/admin.py index 9d1e0b3..702f0e4 100644 --- a/app/dispatch/admin.py +++ b/app/dispatch/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin # Register your models here. -from .models import Load, Customer, Identity, Settings, Invoice, InvoiceItem +from .models import Load, Paperwork, Customer, Identity, Settings, Invoice, InvoiceItem admin.site.register(Load) admin.site.register(Customer) @@ -9,3 +9,4 @@ admin.site.register(Identity) admin.site.register(Invoice) admin.site.register(InvoiceItem) admin.site.register(Settings) +admin.site.register(Paperwork) diff --git a/app/dispatch/migrations/0001_initial.py b/app/dispatch/migrations/0001_initial.py index a358b07..d2aa2f8 100644 --- a/app/dispatch/migrations/0001_initial.py +++ b/app/dispatch/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.5 on 2017-11-02 19:49 +# Generated by Django 1.11.5 on 2017-11-07 19:12 from __future__ import unicode_literals import dispatch.misc diff --git a/app/dispatch/migrations/0002_auto_20171102_1949.py b/app/dispatch/migrations/0002_auto_20171102_1949.py deleted file mode 100644 index 4be45e9..0000000 --- a/app/dispatch/migrations/0002_auto_20171102_1949.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.5 on 2017-11-02 19:49 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('dispatch', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='userinvoicenumber', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='paperwork', - name='load', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Load'), - ), - migrations.AddField( - model_name='load', - name='customer', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Customer'), - ), - migrations.AddField( - model_name='load', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='invoiceitem', - name='invoice', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Invoice'), - ), - migrations.AddField( - model_name='invoice', - name='bill_to', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bill_to', to='dispatch.Identity'), - ), - migrations.AddField( - model_name='invoice', - name='owner', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='dispatch.Identity'), - ), - migrations.AddField( - model_name='invoice', - name='user', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='identity', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/dispatch/migrations/0002_auto_20171107_1912.py b/app/dispatch/migrations/0002_auto_20171107_1912.py new file mode 100644 index 0000000..059d7be --- /dev/null +++ b/app/dispatch/migrations/0002_auto_20171107_1912.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-11-07 19:12 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('dispatch', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name='userinvoicenumber', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='paperwork', + name='load', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Load'), + ), + migrations.AddField( + model_name='load', + name='customer', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Customer'), + ), + migrations.AddField( + model_name='load', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='invoiceitem', + name='invoice', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Invoice'), + ), + migrations.AddField( + model_name='invoice', + name='bill_to', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bill_to', to='dispatch.Identity'), + ), + migrations.AddField( + model_name='invoice', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to='dispatch.Identity'), + ), + migrations.AddField( + model_name='invoice', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='identity', + name='user', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/app/dispatch/migrations/0003_invoice_payment_identifer.py b/app/dispatch/migrations/0003_invoice_payment_identifer.py new file mode 100644 index 0000000..21130a7 --- /dev/null +++ b/app/dispatch/migrations/0003_invoice_payment_identifer.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-11-09 20:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dispatch', '0002_auto_20171107_1912'), + ] + + operations = [ + migrations.AddField( + model_name='invoice', + name='payment_identifer', + field=models.CharField(default='', max_length=256), + ), + ] diff --git a/app/dispatch/models.py b/app/dispatch/models.py index fe0cdb3..ef59fe4 100644 --- a/app/dispatch/models.py +++ b/app/dispatch/models.py @@ -40,6 +40,13 @@ class Load(models.Model): def get_absolute_url(self): return "/loads/view/%i" % self.id + def can_invoice(self): + # Prevent 0 and $1 invoices as well as loads w/o attachments + if self.amount >= 2 and len(self.paperwork_set.all()) >= 1: + return True + else: + return False + class Paperwork(models.Model): load = models.ForeignKey(Load, on_delete=models.CASCADE) @@ -96,6 +103,7 @@ class Invoice(models.Model): invoice_date = models.DateField() due_date = models.DateField() paid = models.BooleanField(default=False) + payment_identifer = models.CharField(default="", max_length=256) def __str__(self): return "Invoice for {} by {} for ${}".format( diff --git a/app/dispatch/monkey_patch.py b/app/dispatch/monkey_patch.py deleted file mode 100644 index cc61b6e..0000000 --- a/app/dispatch/monkey_patch.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.contrib.auth import get_user_model -from django.core.urlresolvers import reverse - -# This should be going away soon with the addition -# of my custom user model - -def get_absolute_url(self): - return reverse('driver_details', kwargs={'pk': self.pk}) - -User.add_to_class("get_absolute_url", get_absolute_url) diff --git a/app/dispatch/templates/dispatch/drivers/detail.html b/app/dispatch/templates/dispatch/drivers/detail.html index 99d9611..e8ffb99 100644 --- a/app/dispatch/templates/dispatch/drivers/detail.html +++ b/app/dispatch/templates/dispatch/drivers/detail.html @@ -51,7 +51,8 @@
{% if request.user.is_superuser %} - Make Default Bill To + Make Default Bill To + {% else %} {% endif %} Edit @@ -60,6 +61,17 @@
+{% if request.user.is_superuser %} +
+
+

+ "Default Bill To" is the Identity object to which all of the Invoices generated + by the system will have their Bill-To set. Be careful when changing this. +

+
+
+{% endif %} +
diff --git a/app/dispatch/templates/dispatch/drivers/summary.html b/app/dispatch/templates/dispatch/drivers/summary.html index 5da7d3c..362c1fa 100644 --- a/app/dispatch/templates/dispatch/drivers/summary.html +++ b/app/dispatch/templates/dispatch/drivers/summary.html @@ -15,10 +15,15 @@
+ {% if can_invoice %} Generate Invoice For Listing + {% else %} + Generate Invoice For Listing + {% endif %}
+{% else %} {% endif %} {% if not request.user.is_superuser %} @@ -30,6 +35,7 @@ + @@ -38,7 +44,17 @@ - + {% if load.amount == 0 %} + + {% else %} + + {% endif %} + {% if load.paperwork_set.all %} + + {% else %} + + {% endif %} +
Date Description TotalAttachments
{{load.date}} {{load.description}}{{load.amount}}{{load.amount}}{{load.amount}}{{load.paperwork_set.all|length}}0 View @@ -52,6 +68,7 @@ {% endif %} +
@@ -71,6 +88,18 @@ +{% if not can_invoice %} +
+
+
Note:
+

+ Cannot generate an invoice at this time, one or more Loads has an improper + Amount or is missing attachments. +

+
+
+{% endif %} + @@ -84,23 +113,9 @@ -{% if stats.incomplete_loads > 0 %} -
-
-
-
- - Note: One or more loads has a 0 amount that needs attention - -
-
-
-
-{% endif %} - {% if request.user.is_superuser %} {% load custom_tags %} - {% listForCommaString "Customer,Amount,Description" as load_headers %} + {% listForCommaString "Customer,Amount,Description,Attachments" as load_headers %} {% include "dispatch/generic_load_listing.html" %} {% endif %} diff --git a/app/dispatch/templates/dispatch/generic_load_listing.html b/app/dispatch/templates/dispatch/generic_load_listing.html index 5512184..3a78335 100644 --- a/app/dispatch/templates/dispatch/generic_load_listing.html +++ b/app/dispatch/templates/dispatch/generic_load_listing.html @@ -28,7 +28,13 @@ {% for load in loads|keyvalue:date %} - + {% if load.amount <= 2 %} + + {% elif load.paperwork_set.all|length <= 0 %} + + {% else %} + + {% endif %} {% if "Customer" in load_headers %} {% if request.user.is_superuser %} @@ -42,7 +48,7 @@ {% if "Amount" in load_headers %} {% if load.amount == 0 %} - + {% else %} {% endif %} @@ -52,6 +58,14 @@ {% endif %} + {% if "Attachments" in load_headers %} + {% if load.paperwork_set.all %} + + {% else %} + + {% endif %} + {% endif %} + + + + + {% if object.paid %} + + {% else %} + + {% endif %} + + {% if object.paid %} + + {% else %} + + {% endif %} + + {% if object.paid %} + + {% else %} + {% endif %}
{{ load.customer.name }}{{ load.amount }}{{ load.amount }}{{ load.amount }}{{ load.description }}{{load.paperwork_set.all|length}}0 View diff --git a/app/dispatch/templates/dispatch/invoice/detail-table.html b/app/dispatch/templates/dispatch/invoice/detail-table.html index 37fa2a5..508ab31 100644 --- a/app/dispatch/templates/dispatch/invoice/detail-table.html +++ b/app/dispatch/templates/dispatch/invoice/detail-table.html @@ -4,6 +4,9 @@ Invoice #{{object.invoice_id}} +
+

PAID

+
@@ -66,10 +69,29 @@
{{object.total}}
Amount Paid:{{object.payment_identifier}}({{object.total}})0
Amount Due: 0{{object.total}}
diff --git a/app/dispatch/templates/dispatch/invoice/detail.html b/app/dispatch/templates/dispatch/invoice/detail.html index 033747c..850c427 100644 --- a/app/dispatch/templates/dispatch/invoice/detail.html +++ b/app/dispatch/templates/dispatch/invoice/detail.html @@ -13,7 +13,8 @@ {% csrf_token %}
Delete - Print + Edit + Print
@@ -21,6 +22,8 @@
+
+
{% include "dispatch/invoice/detail-table.html" %}
@@ -33,7 +36,8 @@ {% csrf_token %} Delete - Print + Edit + Print diff --git a/app/dispatch/templates/dispatch/invoice/edit.html b/app/dispatch/templates/dispatch/invoice/edit.html new file mode 100644 index 0000000..c056ed9 --- /dev/null +++ b/app/dispatch/templates/dispatch/invoice/edit.html @@ -0,0 +1,21 @@ +{% extends 'dispatch/base.html' %} + +{% block title %}Edit - {{ object.description }}{% endblock %} + +{% block content %} +
+
+

{{object.name}}

+
+
+ +
{% csrf_token %} + {% for field in form %} +

+ {{field.label_tag}} {{field}} +

+ {% endfor %} + +
+ +{% endblock %} diff --git a/app/dispatch/templates/dispatch/loads/list.html b/app/dispatch/templates/dispatch/loads/list.html index 01dd47d..615c4e1 100644 --- a/app/dispatch/templates/dispatch/loads/list.html +++ b/app/dispatch/templates/dispatch/loads/list.html @@ -24,7 +24,7 @@ {% load custom_tags %} -{% listForCommaString "Customer,Driver,Amount,Description" as load_headers %} +{% listForCommaString "Customer,Driver,Amount,Description,Attachments" as load_headers %} {% include "dispatch/generic_load_listing.html" %}
diff --git a/app/dispatch/urls.py b/app/dispatch/urls.py index 270c674..14f78f8 100644 --- a/app/dispatch/urls.py +++ b/app/dispatch/urls.py @@ -42,7 +42,7 @@ urlpatterns = [ url(r'^invoices/$', views.InvoiceList.as_view(), name='invoice_list'), url(r'^invoices/view/(?P\d+)$', views.InvoiceDetail.as_view(), name='invoice_detail'), - # url(r'^invoices/edit/(?P\d+)$', views.InvoiceEdit.as_view(), name='invoice_edit'), + url(r'^invoices/edit/(?P\d+)$', views.InvoiceEdit.as_view(), name='invoice_edit'), url(r'^invoices/delete/(?P\d+)$', views.InvoiceDelete.as_view(), name='invoice_delete'), url(r'^drivers/view/(?P\d+)/generate/$', views.InvoiceGenerateForDates, name='invoice_generate'), diff --git a/app/dispatch/views.py b/app/dispatch/views.py index c80607e..b729922 100644 --- a/app/dispatch/views.py +++ b/app/dispatch/views.py @@ -13,7 +13,7 @@ from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.core.urlresolvers import reverse_lazy from dispatch.models import Customer, Load, Paperwork, \ - Invoice, UserInvoiceNumber, Identity + Invoice, UserInvoiceNumber, Identity, Settings from dispatch.forms import AddPaperworkForm from django.contrib.auth import get_user_model from django.contrib.auth.mixins import UserPassesTestMixin @@ -112,6 +112,23 @@ class DriverDetail(UserPassesTestMixin, DetailView): uinv.save() context['invoice_number'] = uinv + # A stupid hack to set a default Bill to Identity on first super user + # login + try: + context['default_bill_to'] = Identity.objects.get(pk=\ + Settings.objects.get(key='default_bill_to').value) + except: + if self.request.user.is_superuser: + try: + print("No default Identity Set") + bill_to = self.request.user.identity + default = Settings(key='default_bill_to', value=bill_to.pk) + default.save() + except Exception as e: + print(e) + print("No identity, you should be redirected") + context['default_bill_to'] = None + return context @@ -130,7 +147,7 @@ class DriverSummary(UserPassesTestMixin, LoadDateSort): stats = {} stats['count'], stats['average'], stats['sum'] = (0, 0, 0) - stats['incomplete_loads'] = 0 + context['can_invoice'] = True loads_by_date = context['loads'] for d in loads_by_date: @@ -138,8 +155,12 @@ class DriverSummary(UserPassesTestMixin, LoadDateSort): for l in loads_by_date[d]: stats['count'] += 1 stats['sum'] += l.amount - if l.amount == 0: - stats['incomplete_loads'] += 1 + + # Any load not up to par will break the chain + print(l) + print(l.can_invoice()) + if not l.can_invoice(): + context['can_invoice'] = False if stats['sum'] is not 0 and stats['count'] is not 0: stats['average'] = stats['sum']/stats['count'] @@ -295,7 +316,7 @@ class LoadDetail(DetailView): return context -class LoadUpdate(FilteredUpdateView): +class LoadUpdate(UserPassesTestMixin, FilteredUpdateView): template_name = "dispatch/loads/edit.html" model = Load fields = [] @@ -325,6 +346,10 @@ class LoadUpdate(FilteredUpdateView): load.user = self.request.user return super(LoadUpdate, self).form_valid(form) + def test_func(self): + return self.request.user.is_superuser or \ + self.get_object().user.pk == self.request.user.pk + class LoadDelete(UserPassesTestMixin, FilteredDeleteView): template_name = "dispatch/loads/delete.html" @@ -502,6 +527,35 @@ class IdentityUpdate(UserPassesTestMixin, UpdateView): # Invoice +class InvoiceEdit(UserPassesTestMixin, FilteredUpdateView): + template_name = "dispatch/invoice/edit.html" + model = Invoice + fields = [] + + default_fields = [] + superuser_fields = ['user', 'owner', 'bill_to', 'invoice_id', + 'invoice_date', 'due_date', 'paid', + 'payment_identifer'] + + def set_fields(self, user): + if user.is_superuser: + self.fields = self.superuser_fields + else: + self.fields = self.default_fields + + def post(self, request, pk): + self.set_fields(request.user) + return super(InvoiceEdit, self).post(request) + + def get(self, request, pk): + self.set_fields(request.user) + return super(InvoiceEdit, self).get(request) + + def test_func(self): + return self.request.user.is_superuser # or \ + # self.get_object().user.pk == self.request.user.pk + + class InvoiceList(FilteredListView): template_name = "dispatch/invoice/list.html" model = Invoice -- cgit v1.2.3