From ea2ce9d3069fa906474e356b7dbd209edd1ea1bd Mon Sep 17 00:00:00 2001 From: Mitch Riedstra Date: Mon, 23 Oct 2017 17:02:36 -0400 Subject: Moved functions to a 'misc' file. Added a settings model. Added initial version of an 'Invoice' model --- README.md | 9 +- app/dispatch/admin.py | 6 +- app/dispatch/migrations/0002_owner.py | 26 ++++ app/dispatch/migrations/0003_settings.py | 23 ++++ app/dispatch/migrations/0004_auto_20171023_2032.py | 53 ++++++++ app/dispatch/migrations/0005_invoice_invoice_id.py | 21 ++++ app/dispatch/migrations/0006_auto_20171023_2049.py | 28 +++++ app/dispatch/misc.py | 37 ++++++ app/dispatch/models.py | 134 +++++++++++++++++++-- .../templates/dispatch/drivers/detail.html | 14 +++ app/dispatch/views.py | 28 +---- 11 files changed, 335 insertions(+), 44 deletions(-) create mode 100644 app/dispatch/migrations/0002_owner.py create mode 100644 app/dispatch/migrations/0003_settings.py create mode 100644 app/dispatch/migrations/0004_auto_20171023_2032.py create mode 100644 app/dispatch/migrations/0005_invoice_invoice_id.py create mode 100644 app/dispatch/migrations/0006_auto_20171023_2049.py create mode 100644 app/dispatch/misc.py diff --git a/README.md b/README.md index 5e03ec1..335ad83 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,18 @@ Requirements going forward: * Drivers will be able to generate a weekly PDF invoice + * Summary page with Invoice-like layout + * Invoice page that lists invoices + * Somehow check for duplicate invoices generated? * Change "user" to Vendor on the "Add Load Page" - * Auto Fill username when not superuser on add load page * Users must be able to initially create new load objects * Change Name To "Load Pay System" -- we're going to think about it * Freight Invoicing System * Weekly Total Invoices from the Company(Driver) to software owner - * Hide Recent Changes on Loads for non-admins? * /loads * Give a warning when any loads don't have paperwork attached to them * Make loads without paperwork blue * Make loads with paperwork green - * /drivers/view - * Give a warning when any loads don't have paperwork attached to them - * Make loads without paperwork blue - * Make loads with paperwork green * A way for users to be invited via email * Figure out if auditlog timestamps are fucked or not with timezones diff --git a/app/dispatch/admin.py b/app/dispatch/admin.py index 617eac4..9d1e0b3 100644 --- a/app/dispatch/admin.py +++ b/app/dispatch/admin.py @@ -1,7 +1,11 @@ from django.contrib import admin # Register your models here. -from .models import Load, Customer +from .models import Load, Customer, Identity, Settings, Invoice, InvoiceItem admin.site.register(Load) admin.site.register(Customer) +admin.site.register(Identity) +admin.site.register(Invoice) +admin.site.register(InvoiceItem) +admin.site.register(Settings) diff --git a/app/dispatch/migrations/0002_owner.py b/app/dispatch/migrations/0002_owner.py new file mode 100644 index 0000000..689ca6b --- /dev/null +++ b/app/dispatch/migrations/0002_owner.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-23 19:14 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dispatch', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Owner', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=256)), + ('address', models.CharField(max_length=256)), + ('city', models.CharField(max_length=256)), + ('state', models.CharField(max_length=256)), + ('zip_code', models.CharField(max_length=256)), + ], + ), + ] diff --git a/app/dispatch/migrations/0003_settings.py b/app/dispatch/migrations/0003_settings.py new file mode 100644 index 0000000..3043e2d --- /dev/null +++ b/app/dispatch/migrations/0003_settings.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-23 19:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dispatch', '0002_owner'), + ] + + operations = [ + migrations.CreateModel( + name='Settings', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('key', models.CharField(max_length=256)), + ('value', models.CharField(max_length=256)), + ], + ), + ] diff --git a/app/dispatch/migrations/0004_auto_20171023_2032.py b/app/dispatch/migrations/0004_auto_20171023_2032.py new file mode 100644 index 0000000..59219bc --- /dev/null +++ b/app/dispatch/migrations/0004_auto_20171023_2032.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-23 20:32 +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): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('dispatch', '0003_settings'), + ] + + operations = [ + migrations.CreateModel( + name='Invoice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.CreateModel( + name='InvoiceItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=256)), + ('quantity', models.IntegerField()), + ('amount', models.DecimalField(decimal_places=2, max_digits=19)), + ('invoice', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dispatch.Invoice')), + ], + ), + migrations.RenameModel( + old_name='Owner', + new_name='Identity', + ), + 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), + ), + ] diff --git a/app/dispatch/migrations/0005_invoice_invoice_id.py b/app/dispatch/migrations/0005_invoice_invoice_id.py new file mode 100644 index 0000000..23b7c49 --- /dev/null +++ b/app/dispatch/migrations/0005_invoice_invoice_id.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-23 20:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dispatch', '0004_auto_20171023_2032'), + ] + + operations = [ + migrations.AddField( + model_name='invoice', + name='invoice_id', + field=models.IntegerField(default=0), + preserve_default=False, + ), + ] diff --git a/app/dispatch/migrations/0006_auto_20171023_2049.py b/app/dispatch/migrations/0006_auto_20171023_2049.py new file mode 100644 index 0000000..04668ff --- /dev/null +++ b/app/dispatch/migrations/0006_auto_20171023_2049.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-10-23 20:49 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('dispatch', '0005_invoice_invoice_id'), + ] + + operations = [ + migrations.AddField( + model_name='invoice', + name='due_date', + field=models.DateField(default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='invoice', + name='invoice_date', + field=models.DateField(default=django.utils.timezone.now), + preserve_default=False, + ), + ] diff --git a/app/dispatch/misc.py b/app/dispatch/misc.py new file mode 100644 index 0000000..14bb5e7 --- /dev/null +++ b/app/dispatch/misc.py @@ -0,0 +1,37 @@ +from django.utils import formats +from datetime import datetime, timedelta +from dateutil import rrule +import uuid + +def get_week_dates(date=None): + week_dates = {} + if date == None: + date = formats.date_format(datetime.now(), "SHORT_DATE_FORMAT") + dt = datetime.strptime(date, '%m/%d/%Y') + weekday = dt.weekday() + if weekday == 6: + week_dates['start_date'] = dt + else: + weekday = weekday + 1 + week_dates['start_date'] = dt - timedelta(days=weekday) + week_dates['end_date'] = week_dates['start_date'] + timedelta(days=6) + week_dates['next_week'] = week_dates['end_date'] + timedelta(days=1) + week_dates['previous_week'] = week_dates['start_date'] - timedelta(days=1) + return week_dates + +def split_loads_by_day(loads,start_date,end_date): + split_loads = {} + + for date in rrule.rrule(rrule.DAILY,dtstart=start_date, until=end_date): + if date not in split_loads: + split_loads[date] = loads.filter(date=date) + + return split_loads + +# This is used to set the upload path of the document for Paperwork Objects +def paperwork_user_directory_path(instance, filename): + # We don't want the UUID to be too long, just enough so there aren't any + # filename conflicts + return 'paperwork/{:d}/'.format(instance.load.pk) + \ + str(uuid.uuid4())[0:9] + filename + diff --git a/app/dispatch/models.py b/app/dispatch/models.py index 6cbaf6c..dfffb9a 100644 --- a/app/dispatch/models.py +++ b/app/dispatch/models.py @@ -2,8 +2,9 @@ from django.db import models from django.conf import settings from auditlog.registry import auditlog from auditlog.models import AuditlogHistoryField - -import uuid +from django.contrib.auth.models import User +from datetime import datetime +from .misc import get_week_dates, paperwork_user_directory_path # Create your models here. @@ -36,14 +37,6 @@ class Load(models.Model): return "/loads/view/%i" % self.id -# This is used to set the upload path of the document for Paperwork Objects -def paperwork_user_directory_path(instance, filename): - # We don't want the UUID to be too long, just enough so there aren't any - # filename conflicts - return 'paperwork/{:d}/'.format(instance.load.pk) + \ - str(uuid.uuid4())[0:9] + filename - - class Paperwork(models.Model): load = models.ForeignKey(Load, on_delete=models.CASCADE) description = models.CharField(max_length=256) @@ -53,5 +46,126 @@ class Paperwork(models.Model): return "%s" % self.load +class Identity(models.Model): + name = models.CharField(max_length=256) + address = models.CharField(max_length=256) + city = models.CharField(max_length=256) + state = models.CharField(max_length=256) + zip_code = models.CharField(max_length=256) + + def __str__(self): + return "{}".format(self.name) + +class Settings(models.Model): + key = models.CharField(max_length=256) + value = models.CharField(max_length=256) + + def __str__(self): + return "{}: {}".format(self.key, self.value) + + +class Invoice(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + owner = models.ForeignKey(Identity, on_delete=models.CASCADE, related_name="owner") + bill_to = models.ForeignKey(Identity, on_delete=models.CASCADE, related_name="bill_to") + invoice_id = models.IntegerField() + invoice_date = models.DateField() + due_date = models.DateField() + + def __str__(self): + return "Invoice for {} by {} for ${}".format( + self.bill_to.name, + self.owner.name, + self.total()) + + def populate_bill_to(self): + pk = Settings.objects.get(key="default_bill_to") + self.bill_to = Identity.objects.get(pk=pk) + + def populate_owner(self): + key = "user_{:d}_identity".format(self.user.id) + pk = Settings.objects.get(key=key) + self.owner = Identity.objects.get(pk=pk) + + def populate_items_default_date(self): + wk = get_week_dates() + return self.populate_items(start_date=wk['start_date'], + end_date=wk['end_date']) + + def populate_items(self, start_date, end_date): + loads = Load.objects.filter(user__exact=self.user.id, date__range=(start_date, end_date)) + for load in loads: + i = InvoiceItem(invoice=self, + description=load.description, + quantity=1, + amount=load.amount) + i.save() + + def clear_items(self): + items = self.items() + for item in items: + item.delete() + + def items(self): + return InvoiceItem.objects.filter(invoice__exact=self.pk) + + def total(self): + t = 0 + for i in InvoiceItem.objects.filter(invoice__exact=self.pk): + t += i.amount + return t + +class InvoiceItem(models.Model): + invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE) + description = models.CharField(max_length=256) + quantity = models.IntegerField() + amount = models.DecimalField(max_digits=19,decimal_places=2) + + def __str__(self): + return "Invoice ID: {} Item: {} for ${}".format( + self.invoice.pk, + self.description, + self.amount) + + + +# class Invoice(): +# load_ids = [] +# total = 0.00 +# +# def __init__(self, **kwargs): +# # self.args = kwargs +# if isinstance(kwargs['driver'], User): +# self.driver = kwargs['driver'] +# else: +# raise Exception("'driver' must be a Django User instance") +# +# if isinstance(kwargs.get('start_date'), datetime) and isinstance(kwargs.get('end_date'), datetime): +# self.start_date = kwargs.get('start_date') +# self.end_date = kwargs.get('end_date') +# else: +# dates = get_week_dates() +# self.start_date = dates['start_date'] +# self.end_date = dates['end_date'] +# +# self.loads = Load.objects.filter(user__exact=self.driver.id, +# date__range=(self.start_date, self.end_date)) +# +# for l in self.loads: +# self.load_ids.append(l.pk) +# self.total += float(l.amount) +# +# if isinstance(kwargs.get('bill_to'), Owner): +# self.bill_to = kwargs.get('bill_to') +# else: +# self.bill_to = Owner.objects.get(pk=Settings.objects.get(key='default_owner').value) +# +# +# def __str__(self): +# return "Invoice for {} from {} for: {}".format(self.end_date, self.driver, self.total) + + + + auditlog.register(Customer) auditlog.register(Load) diff --git a/app/dispatch/templates/dispatch/drivers/detail.html b/app/dispatch/templates/dispatch/drivers/detail.html index 11e07a9..21d2432 100644 --- a/app/dispatch/templates/dispatch/drivers/detail.html +++ b/app/dispatch/templates/dispatch/drivers/detail.html @@ -30,6 +30,20 @@ +
+
+ + + + + + + + +
DateDescriptionTotal
+
+
+
diff --git a/app/dispatch/views.py b/app/dispatch/views.py index 4a8a18b..65d2811 100644 --- a/app/dispatch/views.py +++ b/app/dispatch/views.py @@ -15,38 +15,12 @@ from dispatch.forms import AddPaperworkForm from django.contrib.auth.models import User from django.contrib.auth.mixins import UserPassesTestMixin # from django.http import HttpResponseRedirect -from datetime import datetime, timedelta -from django.utils import formats -from dateutil import rrule +from .misc import get_week_dates, split_loads_by_day import re, os def home(request): return redirect(reverse('load_list')) -def get_week_dates(date=None): - week_dates = {} - if date == None: - date = formats.date_format(datetime.now(), "SHORT_DATE_FORMAT") - dt = datetime.strptime(date, '%m/%d/%Y') - weekday = dt.weekday() - if weekday == 6: - week_dates['start_date'] = dt - else: - weekday = weekday + 1 - week_dates['start_date'] = dt - timedelta(days=weekday) - week_dates['end_date'] = week_dates['start_date'] + timedelta(days=6) - week_dates['next_week'] = week_dates['end_date'] + timedelta(days=1) - week_dates['previous_week'] = week_dates['start_date'] - timedelta(days=1) - return week_dates - -def split_loads_by_day(loads,start_date,end_date): - split_loads = {} - - for date in rrule.rrule(rrule.DAILY,dtstart=start_date, until=end_date): - if date not in split_loads: - split_loads[date] = loads.filter(date=date) - - return split_loads class LoadDateSort(DetailView): def get_context_data(self, **kwargs): -- cgit v1.2.3