diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py new file mode 100644 index 0000000000..627b5f130e --- /dev/null +++ b/InvenTree/order/forms.py @@ -0,0 +1,23 @@ +""" +Django Forms for interacting with Order objects +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from InvenTree.forms import HelperForm + +from .models import PurchaseOrder, PurchaseOrderLineItem + + +class EditPurchaseOrderLineItemForm(HelperForm): + + class Meta: + model = PurchaseOrderLineItem + fields = [ + 'order', + 'part', + 'quantity', + 'reference', + 'received' + ] \ No newline at end of file diff --git a/InvenTree/order/migrations/0005_purchaseorderlineitem_part.py b/InvenTree/order/migrations/0005_purchaseorderlineitem_part.py new file mode 100644 index 0000000000..61f0944156 --- /dev/null +++ b/InvenTree/order/migrations/0005_purchaseorderlineitem_part.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2 on 2019-06-05 10:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0005_auto_20190525_2356'), + ('order', '0004_purchaseorder_status'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorderlineitem', + name='part', + field=models.ForeignKey(blank=True, help_text='Supplier part', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='company.SupplierPart'), + ), + ] diff --git a/InvenTree/order/migrations/0006_auto_20190605_2056.py b/InvenTree/order/migrations/0006_auto_20190605_2056.py new file mode 100644 index 0000000000..e938343d01 --- /dev/null +++ b/InvenTree/order/migrations/0006_auto_20190605_2056.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2019-06-05 10:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0005_auto_20190525_2356'), + ('order', '0005_purchaseorderlineitem_part'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='purchaseorderlineitem', + unique_together={('order', 'part')}, + ), + ] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index e9188dc869..56ef5952fb 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import User from django.utils.translation import ugettext as _ -from company.models import Company +from company.models import Company, SupplierPart from InvenTree.status_codes import OrderStatus @@ -108,11 +108,24 @@ class PurchaseOrderLineItem(OrderLineItem): """ - order = models.ForeignKey(PurchaseOrder, on_delete=models.CASCADE, - related_name='lines', - help_text=_('Purchase Order') - ) + class Meta: + unique_together = ( + ('order', 'part') + ) + + order = models.ForeignKey( + PurchaseOrder, on_delete=models.CASCADE, + related_name='lines', + help_text=_('Purchase Order') + ) # TODO - foreign key references to part and stockitem objects + part = models.ForeignKey( + SupplierPart, on_delete=models.SET_NULL, + blank=True, null=True, + related_name='orders', + help_text=_("Supplier part"), + ) + received = models.PositiveIntegerField(default=0, help_text=_('Number of items received')) diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index bea88a21ba..2d40de6459 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -1,9 +1,106 @@ {% extends "base.html" %} -{% block content %} +{% load static %} -Purchase Order: {{ order.reference }} -

-Description: {{ order.description }} +{% block page_title %} +InvenTree | {{ order }} +{% endblock %} + +{% block content %} +
+
+
+
+ +
+
+

{{ order }}

+

{{ order.description }}

+ {% if order.URL %} + {{ order.URL }} + {% endif %} +
+
+
+
+ + + + + + + + + + + + + + {% if order.issue_date %} + + + + + {% endif %} +
Status{% include "order/order_status.html" %}
Created{{ order.creation_date }}
Created By{{ order.created_by }}
Issued{{ order.issue_date }}
+
+
+ +
+ +

Order Items

+ + + + + + + + + + + + {% for line in order.lines.all %} + + + + + + + + {% endfor %} +
LinePartReferenceQuantityReceived
{{ forloop.counter }}{{ line.reference }}{{ line.quantity }}{{ line.received }}
+ +{% if order.notes %} +
+
+
Notes
+
{{ order.notes }}
+
+{% endif %} + +{% endblock %} + +{% block js_ready %} + +$('#new-po-line').click(function() { + launchModalForm("{% url 'po-line-item-create' %}", + { + reload: true, + data: { + order: {{ order.id }}, + }, + } + ); +}); + +$("#po-lines-table").bootstrapTable({ +}); {% endblock %} \ No newline at end of file diff --git a/InvenTree/order/templates/order/purchase_orders.html b/InvenTree/order/templates/order/purchase_orders.html index f85de6c962..4e48effa59 100644 --- a/InvenTree/order/templates/order/purchase_orders.html +++ b/InvenTree/order/templates/order/purchase_orders.html @@ -2,6 +2,10 @@ {% load static %} +{% block page_title %} +InvenTree | Purchase Orders +{% endblock %} + {% block content %}

Purchase Orders

diff --git a/InvenTree/order/urls.py b/InvenTree/order/urls.py index 961cbb6857..9aa350fc54 100644 --- a/InvenTree/order/urls.py +++ b/InvenTree/order/urls.py @@ -14,11 +14,18 @@ purchase_order_detail_urls = [ url(r'^.*$', views.PurchaseOrderDetail.as_view(), name='purchase-order-detail'), ] +po_line_urls = [ + + url(r'^new/', views.POLineItemCreate.as_view(), name='po-line-item-create'), +] + purchase_order_urls = [ # Display detail view for a single purchase order url(r'^(?P\d+)/', include(purchase_order_detail_urls)), + url(r'^line/', include(po_line_urls)), + # Display complete list of purchase orders url(r'^.*$', views.PurchaseOrderIndex.as_view(), name='purchase-order-index'), ] diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index add27967d7..ac84ee1f4f 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -7,7 +7,10 @@ from __future__ import unicode_literals from django.views.generic import DetailView, ListView -from .models import PurchaseOrder +from .models import PurchaseOrder, PurchaseOrderLineItem +from .forms import EditPurchaseOrderLineItemForm + +from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.status_codes import OrderStatus @@ -19,8 +22,8 @@ class PurchaseOrderIndex(ListView): template_name = 'order/purchase_orders.html' context_object_name = 'orders' - def get_context_data(self): - ctx = super().get_context_data() + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) ctx['OrderStatus'] = OrderStatus @@ -33,3 +36,76 @@ class PurchaseOrderDetail(DetailView): context_object_name = 'order' queryset = PurchaseOrder.objects.all().prefetch_related('lines') template_name = 'order/purchase_order_detail.html' + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + + ctx['OrderStatus'] = OrderStatus + + return ctx + + +class POLineItemCreate(AjaxCreateView): + """ AJAX view for creating a new PurchaseOrderLineItem object + """ + + model = PurchaseOrderLineItem + context_object_name = 'line' + form_class = EditPurchaseOrderLineItemForm + ajax_template_name = 'modal_form.html' + ajax_form_action = 'Add Line Item' + + def get_form(self): + """ Limit choice options based on the selected order, etc + """ + + form = super().get_form() + + order_id = form['order'].value() + + try: + order = PurchaseOrder.objects.get(id=order_id) + + query = form.fields['part'].queryset + + # Only allow parts from the selected supplier + query = query.filter(supplier=order.supplier.id) + + print('limiting queryset') + + form.fields['part'].queryset = query + except PurchaseOrder.DoesNotExist: + print('error') + pass + + return form + + + def get_initial(self): + """ Extract initial data for the line item. + + - The 'order' will be passed as a query parameter + - Use this to set the 'order' field and limit the options for 'part' + """ + + initials = super().get_initial().copy() + + order_id = self.request.GET.get('order', None) + + if order_id: + try: + order = PurchaseOrder.objects.get(id=order_id) + initials['order'] = order + + except PurchaseOrder.DoesNotExist: + pass + + return initials + + +class POLineItemEdit(AjaxUpdateView): + + model = PurchaseOrderLineItem + form_class = EditPurchaseOrderLineItemForm + ajax_template_name = 'modal_form.html' + ajax_form_action = 'Edit Line Item' diff --git a/InvenTree/static/css/inventree.css b/InvenTree/static/css/inventree.css index da7d38d250..794676ca68 100644 --- a/InvenTree/static/css/inventree.css +++ b/InvenTree/static/css/inventree.css @@ -297,6 +297,10 @@ margin-bottom: 5px; } +.panel-body { + padding: 10px; +} + .panel-group .panel { border-radius: 2px; }