Updates for purchase order line items

- Display list of line items
- Add a form to create a new line item
This commit is contained in:
Oliver Walters 2019-06-05 20:59:30 +10:00
parent e199ed2281
commit be6b1ae2f8
9 changed files with 274 additions and 12 deletions

23
InvenTree/order/forms.py Normal file
View File

@ -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'
]

View File

@ -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'),
),
]

View File

@ -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')},
),
]

View File

@ -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'))

View File

@ -1,9 +1,106 @@
{% extends "base.html" %}
{% block content %}
{% load static %}
Purchase Order: {{ order.reference }}
<br><hr>
Description: {{ order.description }}
{% block page_title %}
InvenTree | {{ order }}
{% endblock %}
{% block content %}
<div class='row'>
<div class='col-sm-6'>
<div class='media'>
<div class='media-left'>
<img class='part-thumb'
{% if order.supplier.image %}
src="{{ order.supplier.image.url }}"
{% else %}
src="{% static 'img/blank_image.png' %}"
{% endif %}
/>
</div>
<div class='media-body'>
<h4>{{ order }}</h4>
<p>{{ order.description }}</p>
{% if order.URL %}
<a href="{{ order.URL }}">{{ order.URL }}</a>
{% endif %}
</div>
</div>
</div>
<div class='col-sm-6'>
<table class='table'>
<tr>
<td>Status</td>
<td>{% include "order/order_status.html" %}</td>
</tr>
<tr>
<td>Created</td>
<td>{{ order.creation_date }}</td>
</tr>
<tr>
<td>Created By</td>
<td>{{ order.created_by }}</td>
</tr>
{% if order.issue_date %}
<tr>
<td>Issued</td>
<td>{{ order.issue_date }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
<hr>
<h4>Order Items</h4>
<button type='button' class='btn btn-default' id='new-po-line'>Add Line Item</button>
<table class='table table-striped table-condensed' id='po-lines-table'>
<tr>
<th data-field='line'>Line</th>
<th data-field='part'>Part</th>
<th data-field='reference'>Reference</th>
<th data-field='quantity'>Quantity</th>
<th data-field='received'>Received</th>
</tr>
{% for line in order.lines.all %}
<tr>
<td>{{ forloop.counter }}</td>
<td></td>
<td>{{ line.reference }}</td>
<td>{{ line.quantity }}</td>
<td>{{ line.received }}</td>
</tr>
{% endfor %}
</table>
{% if order.notes %}
<hr>
<div class='panel panel-default'>
<div class='panel-heading'><b>Notes</b></div>
<div class='panel-body'>{{ order.notes }}</div>
</div>
{% 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 %}

View File

@ -2,6 +2,10 @@
{% load static %}
{% block page_title %}
InvenTree | Purchase Orders
{% endblock %}
{% block content %}
<h4>Purchase Orders</h4>

View File

@ -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<pk>\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'),
]

View File

@ -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'

View File

@ -297,6 +297,10 @@
margin-bottom: 5px;
}
.panel-body {
padding: 10px;
}
.panel-group .panel {
border-radius: 2px;
}