From 5769befb04d6af73458e0e9b4faf5af9c8b2965c Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 18 Apr 2018 00:22:25 +1000 Subject: [PATCH 1/4] Add model for supplier order (PO) - Added create page - Added detail page --- InvenTree/supplier/forms.py | 23 ++++++++++ .../migrations/0009_auto_20180417_1411.py | 42 +++++++++++++++++ .../migrations/0010_auto_20180417_1420.py | 20 +++++++++ InvenTree/supplier/models.py | 45 +++++++++++++++++++ .../templates/supplier/order_create.html | 5 +++ .../templates/supplier/order_detail.html | 7 +++ InvenTree/supplier/urls.py | 26 +++++++++-- InvenTree/supplier/views.py | 15 +++++++ 8 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 InvenTree/supplier/migrations/0009_auto_20180417_1411.py create mode 100644 InvenTree/supplier/migrations/0010_auto_20180417_1420.py create mode 100644 InvenTree/supplier/templates/supplier/order_create.html create mode 100644 InvenTree/supplier/templates/supplier/order_detail.html diff --git a/InvenTree/supplier/forms.py b/InvenTree/supplier/forms.py index 10167ee8d0..b154d209ea 100644 --- a/InvenTree/supplier/forms.py +++ b/InvenTree/supplier/forms.py @@ -3,6 +3,29 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Submit from .models import Supplier, SupplierPart +from .models import SupplierOrder + + +class EditSupplierOrderForm(forms.ModelForm): + + def __init__(self, *args, **kwargs): + super(EditSupplierOrderForm, self).__init__(*args, **kwargs) + self.helper = FormHelper() + + self.helper.form_id = 'id-edit-part-form' + self.helper.form_class = 'blueForms' + self.helper.form_method = 'post' + + self.helper.add_input(Submit('submit', 'Submit')) + + class Meta: + model = SupplierOrder + fields = [ + 'internal_ref', + 'supplier', + 'notes', + 'issued_date', + ] class EditSupplierForm(forms.ModelForm): diff --git a/InvenTree/supplier/migrations/0009_auto_20180417_1411.py b/InvenTree/supplier/migrations/0009_auto_20180417_1411.py new file mode 100644 index 0000000000..b89f2adcf6 --- /dev/null +++ b/InvenTree/supplier/migrations/0009_auto_20180417_1411.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2018-04-17 14:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('supplier', '0008_delete_customer'), + ] + + operations = [ + migrations.CreateModel( + name='SupplierOrder', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('internal_ref', models.CharField(max_length=25, unique=True)), + ('created_date', models.DateField(auto_now_add=True)), + ('issued_date', models.DateField(blank=True, help_text='Date the purchase order was issued')), + ('notes', models.TextField(blank=True, help_text='Order notes')), + ('supplier', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='supplier.Supplier')), + ], + ), + migrations.CreateModel( + name='SupplierOrderLineItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('line_number', models.PositiveIntegerField(default=1)), + ('quantity', models.PositiveIntegerField(default=1)), + ('notes', models.TextField(blank=True)), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='supplier.SupplierOrder')), + ('part', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='supplier.SupplierPart')), + ], + ), + migrations.AlterUniqueTogether( + name='supplierorderlineitem', + unique_together=set([('order', 'line_number'), ('order', 'part')]), + ), + ] diff --git a/InvenTree/supplier/migrations/0010_auto_20180417_1420.py b/InvenTree/supplier/migrations/0010_auto_20180417_1420.py new file mode 100644 index 0000000000..a8e8a8436c --- /dev/null +++ b/InvenTree/supplier/migrations/0010_auto_20180417_1420.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2018-04-17 14:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('supplier', '0009_auto_20180417_1411'), + ] + + operations = [ + migrations.AlterField( + model_name='supplierorder', + name='issued_date', + field=models.DateField(blank=True, help_text='Date the purchase order was issued', null=True), + ), + ] diff --git a/InvenTree/supplier/models.py b/InvenTree/supplier/models.py index c3ef7a57f3..83c3a4b850 100644 --- a/InvenTree/supplier/models.py +++ b/InvenTree/supplier/models.py @@ -103,3 +103,48 @@ class SupplierPriceBreak(models.Model): cost=self.cost, currency=self.currency if self.currency else '', quan=self.quantity) + + +class SupplierOrder(models.Model): + """ + An order of parts from a supplier, made up of multiple line items + """ + + def get_absolute_url(self): + return "/supplier/order/{id}/".format(id=self.id) + + # Interal reference for this order + internal_ref = models.CharField(max_length=25, unique=True) + + supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE) + + created_date = models.DateField(auto_now_add=True, editable=False) + + issued_date = models.DateField(blank=True, null=True, help_text="Date the purchase order was issued") + + notes = models.TextField(blank=True, help_text="Order notes") + + def __str__(self): + return "PO {ref}".format(ref=self.internal_ref) + + +class SupplierOrderLineItem(models.Model): + """ + A line item in a supplier order, corresponding to some quantity of part + """ + + class Meta: + unique_together = [ + ('order', 'line_number'), + ('order', 'part'), + ] + + order = models.ForeignKey(SupplierOrder, on_delete=models.CASCADE) + + line_number = models.PositiveIntegerField(default=1) + + part = models.ForeignKey(SupplierPart, null=True, blank=True, on_delete=models.SET_NULL) + + quantity = models.PositiveIntegerField(default=1) + + notes = models.TextField(blank=True) diff --git a/InvenTree/supplier/templates/supplier/order_create.html b/InvenTree/supplier/templates/supplier/order_create.html new file mode 100644 index 0000000000..d401662ea3 --- /dev/null +++ b/InvenTree/supplier/templates/supplier/order_create.html @@ -0,0 +1,5 @@ +{% extends "create_edit_obj.html" %} + +{% block obj_title %} +Create a new supplier purchase order +{% endblock %} \ No newline at end of file diff --git a/InvenTree/supplier/templates/supplier/order_detail.html b/InvenTree/supplier/templates/supplier/order_detail.html new file mode 100644 index 0000000000..1a9f650410 --- /dev/null +++ b/InvenTree/supplier/templates/supplier/order_detail.html @@ -0,0 +1,7 @@ +{% include "base.html" %} + +{% block content %} + +Details for supplier order {{ order.id }}. + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/supplier/urls.py b/InvenTree/supplier/urls.py index 94321f734e..16b870a086 100644 --- a/InvenTree/supplier/urls.py +++ b/InvenTree/supplier/urls.py @@ -61,10 +61,30 @@ supplier_part_detail_urls = [ url('^.*$', views.SupplierPartDetail.as_view(), name='supplier-part-detail'), ] -supplier_urls = [ - url(r'part/(?P\d+)/', include(supplier_part_detail_urls)), +supplier_part_urls = [ + url(r'^new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'), - url(r'part/new/?', views.SupplierPartCreate.as_view(), name='supplier-part-create'), + url(r'^(?P\d+)/', include(supplier_part_detail_urls)), +] + +supplier_order_detail_urls = [ + + + url('^.*$', views.SupplierOrderDetail.as_view(), name='supplier-order-detail'), +] + +supplier_order_urls = [ + url(r'^new/?', views.SupplierOrderCreate.as_view(), name='supplier-order-create'), + + url(r'^(?P\d+)/', include(supplier_order_detail_urls)), +] + +supplier_urls = [ + + + url(r'part/', include(supplier_part_urls)), + + url(r'order/', include(supplier_order_urls)), url(r'new/?', views.SupplierCreate.as_view(), name='supplier-create'), diff --git a/InvenTree/supplier/views.py b/InvenTree/supplier/views.py index c64a0b07dc..c49b18586e 100644 --- a/InvenTree/supplier/views.py +++ b/InvenTree/supplier/views.py @@ -6,9 +6,24 @@ from django.views.generic.edit import UpdateView, DeleteView, CreateView from part.models import Part from .models import Supplier, SupplierPart +from .models import SupplierOrder from .forms import EditSupplierForm from .forms import EditSupplierPartForm +from .forms import EditSupplierOrderForm + +class SupplierOrderDetail(DetailView): + context_object_name = 'order' + model = SupplierOrder + template_name = 'supplier/order_detail.html' + queryset = SupplierOrder.objects.all() + + +class SupplierOrderCreate(CreateView): + model = SupplierOrder + form_class = EditSupplierOrderForm + context_object_name = 'supplier' + template_name = 'supplier/order_create.html' class SupplierIndex(ListView): From 7045443d7b0146802673aade2e572dd378165a44 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 18 Apr 2018 00:42:44 +1000 Subject: [PATCH 2/4] Add tab view for supplier page - "Parts" tab - "Orders" tab --- InvenTree/InvenTree/models.py | 17 ++++-- .../migrations/0002_auto_20180417_1436.py | 35 +++++++++++ InvenTree/part/templates/part/tabs.html | 24 +++++--- InvenTree/supplier/admin.py | 7 ++- .../migrations/0011_auto_20180417_1436.py | 61 +++++++++++++++++++ InvenTree/supplier/models.py | 3 +- .../supplier/templates/supplier/detail.html | 44 ++----------- .../supplier/templates/supplier/orders.html | 24 ++++++++ .../templates/supplier/supplier_base.html | 46 ++++++++++++++ .../supplier/templates/supplier/tabs.html | 6 ++ InvenTree/supplier/urls.py | 2 + InvenTree/templates/navbar.html | 2 +- 12 files changed, 216 insertions(+), 55 deletions(-) create mode 100644 InvenTree/customer/migrations/0002_auto_20180417_1436.py create mode 100644 InvenTree/supplier/migrations/0011_auto_20180417_1436.py create mode 100644 InvenTree/supplier/templates/supplier/orders.html create mode 100644 InvenTree/supplier/templates/supplier/supplier_base.html create mode 100644 InvenTree/supplier/templates/supplier/tabs.html diff --git a/InvenTree/InvenTree/models.py b/InvenTree/InvenTree/models.py index 06bf758b84..a066c2b281 100644 --- a/InvenTree/InvenTree/models.py +++ b/InvenTree/InvenTree/models.py @@ -15,18 +15,25 @@ class Company(models.Model): class Meta: abstract = True - name = models.CharField(max_length=100, unique=True) + name = models.CharField(max_length=100, unique=True, + help_text='Company naem') + description = models.CharField(max_length=500) - website = models.URLField(blank=True) + + website = models.URLField(blank=True, help_text='Company website URL') + address = models.CharField(max_length=200, - blank=True) + blank=True, help_text='Company address') + phone = models.CharField(max_length=50, blank=True) + email = models.EmailField(blank=True) + contact = models.CharField(max_length=100, blank=True) - notes = models.CharField(max_length=500, - blank=True) + + notes = models.TextField(blank=True) def __str__(self): return self.name diff --git a/InvenTree/customer/migrations/0002_auto_20180417_1436.py b/InvenTree/customer/migrations/0002_auto_20180417_1436.py new file mode 100644 index 0000000000..d10f324db6 --- /dev/null +++ b/InvenTree/customer/migrations/0002_auto_20180417_1436.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2018-04-17 14:36 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('customer', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='customer', + name='address', + field=models.CharField(blank=True, help_text='Company address', max_length=200), + ), + migrations.AlterField( + model_name='customer', + name='name', + field=models.CharField(help_text='Company naem', max_length=100, unique=True), + ), + migrations.AlterField( + model_name='customer', + name='notes', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='customer', + name='website', + field=models.URLField(blank=True, help_text='Company website URL'), + ), + ] diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index da0b1c9ec2..6c3aff79dd 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -1,23 +1,31 @@ \ No newline at end of file From f4aa09a35477cc0bc3d664056f8e3e32ea2ed6d5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 18 Apr 2018 00:50:05 +1000 Subject: [PATCH 3/4] Added 'status' and 'delivery_date' fields to SupplierOrder --- InvenTree/supplier/admin.py | 2 +- .../migrations/0012_auto_20180417_1447.py | 25 +++++++++++++++++++ InvenTree/supplier/models.py | 25 ++++++++++++++++++- .../supplier/templates/supplier/orders.html | 4 ++- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 InvenTree/supplier/migrations/0012_auto_20180417_1447.py diff --git a/InvenTree/supplier/admin.py b/InvenTree/supplier/admin.py index f072bed9cf..70be9abf88 100644 --- a/InvenTree/supplier/admin.py +++ b/InvenTree/supplier/admin.py @@ -17,7 +17,7 @@ class SupplierPartAdmin(ImportExportModelAdmin): class SupplierOrderAdmin(admin.ModelAdmin): - list_display = ('internal_ref', 'supplier', 'issued_date') + list_display = ('internal_ref', 'supplier', 'issued_date', 'delivery_date', 'status') admin.site.register(Supplier, SupplierAdmin) diff --git a/InvenTree/supplier/migrations/0012_auto_20180417_1447.py b/InvenTree/supplier/migrations/0012_auto_20180417_1447.py new file mode 100644 index 0000000000..2423b723ae --- /dev/null +++ b/InvenTree/supplier/migrations/0012_auto_20180417_1447.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.12 on 2018-04-17 14:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('supplier', '0011_auto_20180417_1436'), + ] + + operations = [ + migrations.AddField( + model_name='supplierorder', + name='delivery_date', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='supplierorder', + name='status', + field=models.PositiveIntegerField(choices=[(40, 'Cancelled'), (10, 'Pending'), (20, 'Placed'), (50, 'Lost'), (30, 'Received')], default=10), + ), + ] diff --git a/InvenTree/supplier/models.py b/InvenTree/supplier/models.py index 2cb760c8e8..a3917a4997 100644 --- a/InvenTree/supplier/models.py +++ b/InvenTree/supplier/models.py @@ -1,5 +1,8 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals +from django.utils.translation import ugettext as _ + from django.db import models from django.core.validators import MinValueValidator @@ -126,7 +129,27 @@ class SupplierOrder(models.Model): notes = models.TextField(blank=True, help_text="Order notes") def __str__(self): - return "PO {ref}".format(ref=self.internal_ref) + return "PO {ref} ({status})".format(ref=self.internal_ref, + status=self.get_status_display) + + PENDING = 10 # Order is pending (not yet placed) + PLACED = 20 # Order has been placed + RECEIVED = 30 # Order has been received + CANCELLED = 40 # Order was cancelled + LOST = 50 # Order was lost + + ORDER_STATUS_CODES = {PENDING: _("Pending"), + PLACED: _("Placed"), + CANCELLED: _("Cancelled"), + RECEIVED: _("Received"), + LOST: _("Lost") + } + + status = models.PositiveIntegerField(default=PENDING, + choices=ORDER_STATUS_CODES.items()) + + delivery_date = models.DateField(blank=True, null=True) + class SupplierOrderLineItem(models.Model): diff --git a/InvenTree/supplier/templates/supplier/orders.html b/InvenTree/supplier/templates/supplier/orders.html index 425eced0f7..84adf17585 100644 --- a/InvenTree/supplier/templates/supplier/orders.html +++ b/InvenTree/supplier/templates/supplier/orders.html @@ -10,13 +10,15 @@ Reference Issued + Delivery Status {% for order in supplier.orders.all %} {{ order.internal_ref }} {% if order.issued_date %}{{ order.issued_date }}{% endif %} - Status + {% if order.delivery_date %}{{ order.delivery_date }}{% endif %} + {{ order.get_status_display }} {% endfor %} From d00f9f74e4d80deb688d2df0d9721ee7cb16adb7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 18 Apr 2018 01:13:42 +1000 Subject: [PATCH 4/4] Added details page for SupplierOrder --- InvenTree/build/templates/build/detail.html | 2 +- .../templates/customer/order_index.html | 2 +- .../templates/supplier/order_detail.html | 47 ++++++++++++++++++- .../templates/supplier/order_status.html | 13 +++++ .../supplier/templates/supplier/orders.html | 2 +- InvenTree/templates/base.html | 2 +- 6 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 InvenTree/supplier/templates/supplier/order_status.html diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index f8500b0fe0..c412b1ea75 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -1,4 +1,4 @@ -{% include "base.html" %} +{% extends "base.html" %} {% block content %}

Build Details

diff --git a/InvenTree/customer/templates/customer/order_index.html b/InvenTree/customer/templates/customer/order_index.html index b71db4932b..2bc8554196 100644 --- a/InvenTree/customer/templates/customer/order_index.html +++ b/InvenTree/customer/templates/customer/order_index.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% block contents %} +{% block content %} diff --git a/InvenTree/supplier/templates/supplier/order_detail.html b/InvenTree/supplier/templates/supplier/order_detail.html index 1a9f650410..1aa3d8c8e9 100644 --- a/InvenTree/supplier/templates/supplier/order_detail.html +++ b/InvenTree/supplier/templates/supplier/order_detail.html @@ -1,7 +1,50 @@ -{% include "base.html" %} +{% extends "base.html" %} {% block content %} -Details for supplier order {{ order.id }}. +

Supplier Order Details

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
Reference{{ order.internal_ref }}
Supplier + {% if order.supplier %} + {{ order.supplier.name }} + {% endif %} +
Status{% include "supplier/order_status.html" with order=order %}
Created Date{{ order.created_date }}
Issued Date{{ order.issued_date }}
Delivered Date{{ order.delivery_date }}
+ + +{% if order.notes %} +
+
Notes
+
{{ order.notes }}
+
+ +

TODO

+Here we list all the line ites which exist under this order... + +{% endif %} {% endblock %} \ No newline at end of file diff --git a/InvenTree/supplier/templates/supplier/order_status.html b/InvenTree/supplier/templates/supplier/order_status.html new file mode 100644 index 0000000000..49ccc7f170 --- /dev/null +++ b/InvenTree/supplier/templates/supplier/order_status.html @@ -0,0 +1,13 @@ +{% if order.status == order.PENDING %} + +{% elif order.status == order.PLACED %} + +{% elif order.status == order.RECEIVED %} + +{% elif order.status == order.CANCELLED %} + +{% else %} + +{% endif %} +{{ order.get_status_display }} + \ No newline at end of file diff --git a/InvenTree/supplier/templates/supplier/orders.html b/InvenTree/supplier/templates/supplier/orders.html index 84adf17585..29ac20dad0 100644 --- a/InvenTree/supplier/templates/supplier/orders.html +++ b/InvenTree/supplier/templates/supplier/orders.html @@ -18,7 +18,7 @@ {{ order.internal_ref }} {% if order.issued_date %}{{ order.issued_date }}{% endif %} {% if order.delivery_date %}{{ order.delivery_date }}{% endif %} - {{ order.get_status_display }} + {% include "supplier/order_status.html" with order=order %} {% endfor %} diff --git a/InvenTree/templates/base.html b/InvenTree/templates/base.html index 0124c9018d..3151fd1477 100644 --- a/InvenTree/templates/base.html +++ b/InvenTree/templates/base.html @@ -33,7 +33,7 @@ InvenTree {% include "navbar.html" %} -
+
{% block content %} {% endblock %}