diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css
index 19b92e2817..bdfeb42f4c 100644
--- a/InvenTree/InvenTree/static/css/inventree.css
+++ b/InvenTree/InvenTree/static/css/inventree.css
@@ -116,7 +116,7 @@
}
.icon-green {
- color: #5c5;
+ color: #43bb43;
}
.icon-blue {
diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py
index 701c1faabf..780492cdd5 100644
--- a/InvenTree/company/serializers.py
+++ b/InvenTree/company/serializers.py
@@ -64,15 +64,11 @@ class CompanySerializer(InvenTreeModelSerializer):
class SupplierPartSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPart object """
- url = serializers.CharField(source='get_absolute_url', read_only=True)
-
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
supplier_detail = CompanyBriefSerializer(source='supplier', many=False, read_only=True)
manufacturer_detail = CompanyBriefSerializer(source='manufacturer', many=False, read_only=True)
- pricing = serializers.CharField(source='unit_pricing', read_only=True)
-
def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False)
@@ -94,7 +90,6 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
model = SupplierPart
fields = [
'pk',
- 'url',
'part',
'part_detail',
'supplier',
@@ -105,7 +100,6 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
'description',
'MPN',
'link',
- 'pricing',
]
diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py
index 08a69f7dec..d8568ddf3b 100644
--- a/InvenTree/order/api.py
+++ b/InvenTree/order/api.py
@@ -162,6 +162,17 @@ class POLineItemList(generics.ListCreateAPIView):
queryset = PurchaseOrderLineItem.objects.all()
serializer_class = POLineItemSerializer
+ def get_serializer(self, *args, **kwargs):
+
+ try:
+ kwargs['part_detail'] = str2bool(self.request.query_params.get('part_detail', False))
+ except AttributeError:
+ pass
+
+ kwargs['context'] = self.get_serializer_context()
+
+ return self.serializer_class(*args, **kwargs)
+
permission_classes = [
permissions.IsAuthenticated,
]
diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py
index d448e0d444..2eadd59138 100644
--- a/InvenTree/order/models.py
+++ b/InvenTree/order/models.py
@@ -417,6 +417,10 @@ class PurchaseOrderLineItem(OrderLineItem):
help_text=_('Purchase Order')
)
+ def get_base_part(self):
+ """ Return the base-part for the line item """
+ return self.part.part
+
# TODO - Function callback for when the SupplierPart is deleted?
part = models.ForeignKey(
diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py
index df3b68c924..992e05a80c 100644
--- a/InvenTree/order/serializers.py
+++ b/InvenTree/order/serializers.py
@@ -10,7 +10,7 @@ from rest_framework import serializers
from django.db.models import Count
from InvenTree.serializers import InvenTreeModelSerializer
-from company.serializers import CompanyBriefSerializer
+from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
from part.serializers import PartBriefSerializer
from .models import PurchaseOrder, PurchaseOrderLineItem
@@ -74,6 +74,22 @@ class POSerializer(InvenTreeModelSerializer):
class POLineItemSerializer(InvenTreeModelSerializer):
+ def __init__(self, *args, **kwargs):
+
+ part_detail = kwargs.pop('part_detail', False)
+
+ super().__init__(*args, **kwargs)
+
+ if part_detail is not True:
+ self.fields.pop('part_detail')
+ self.fields.pop('supplier_part_detail')
+
+ quantity = serializers.FloatField()
+ received = serializers.FloatField()
+
+ part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True)
+ supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True)
+
class Meta:
model = PurchaseOrderLineItem
@@ -84,6 +100,8 @@ class POLineItemSerializer(InvenTreeModelSerializer):
'notes',
'order',
'part',
+ 'part_detail',
+ 'supplier_part_detail',
'received',
]
diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html
index 64bf832c02..3f76d98a77 100644
--- a/InvenTree/order/templates/order/order_base.html
+++ b/InvenTree/order/templates/order/order_base.html
@@ -28,16 +28,16 @@ src="{% static 'img/blank_image.png' %}"
- {% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
+ {% if order.status == PurchaseOrderStatus.PENDING and order.lines.count > 0 %}
- {% elif order.status == OrderStatus.PLACED %}
+ {% elif order.status == PurchaseOrderStatus.PLACED %}
@@ -45,9 +45,9 @@ src="{% static 'img/blank_image.png' %}"
{% endif %}
- {% if order.status == OrderStatus.PENDING or order.status == OrderStatus.PLACED %}
+ {% if order.status == PurchaseOrderStatus.PENDING or order.status == PurchaseOrderStatus.PLACED %}
{% endif %}
@@ -100,7 +100,7 @@ src="{% static 'img/blank_image.png' %}"
{{ order.issue_date }} |
{% endif %}
- {% if order.status == OrderStatus.COMPLETE %}
+ {% if order.status == PurchaseOrderStatus.COMPLETE %}
|
{% trans "Received" %} |
@@ -113,7 +113,7 @@ src="{% static 'img/blank_image.png' %}"
{% block js_ready %}
{{ block.super }}
-{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
+{% if order.status == PurchaseOrderStatus.PENDING and order.lines.count > 0 %}
$("#place-order").click(function() {
launchModalForm("{% url 'po-issue' order.id %}",
{
diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html
index 444da8f88f..73c794126c 100644
--- a/InvenTree/order/templates/order/purchase_order_detail.html
+++ b/InvenTree/order/templates/order/purchase_order_detail.html
@@ -12,73 +12,14 @@
- {% if order.status == OrderStatus.PENDING %}
+ {% if order.status == PurchaseOrderStatus.PENDING %}
{% endif %}
{% trans "Purchase Order Items" %}
-
-
-
- {% trans "Line" %} |
- {% trans "Part" %} |
- {% trans "Description" %} |
- {% trans "Order Code" %} |
- {% trans "Reference" %} |
- {% trans "Quantity" %} |
- {% if not order.status == OrderStatus.PENDING %}
- {% trans "Received" %} |
- {% endif %}
- {% trans "Note" %} |
- |
-
-
-
- {% for line in order.lines.all %}
-
-
- {{ forloop.counter }}
- |
- {% if line.part %}
-
- {% include "hover_image.html" with image=line.part.part.image hover=True %}
- {{ line.part.part.full_name }}
- |
- {{ line.part.part.description }} |
- {{ line.part.SKU }} |
- {% else %}
- Warning: Part has been deleted. |
- {% endif %}
- {{ line.reference }} |
- {% decimal line.quantity %} |
- {% if not order.status == OrderStatus.PENDING %}
- {% decimal line.received %} |
- {% endif %}
-
- {{ line.notes }}
- |
-
-
- {% if order.status == OrderStatus.PENDING %}
-
-
- {% endif %}
- {% if order.status == OrderStatus.PLACED and line.received < line.quantity %}
-
- {% endif %}
-
- |
-
- {% endfor %}
-
+
{% endblock %}
@@ -87,27 +28,6 @@
{{ block.super }}
-$("#po-lines-table").on('click', ".line-receive", function() {
-
- var button = $(this);
-
- console.log('clicked! ' + button.attr('pk'));
-
- launchModalForm("{% url 'po-receive' order.id %}", {
- reload: true,
- data: {
- line: button.attr('pk')
- },
- secondary: [
- {
- field: 'location',
- label: 'New Location',
- title: 'Create new stock location',
- url: "{% url 'stock-location-create' %}",
- },
- ]
- });
-});
$("#receive-order").click(function() {
launchModalForm("{% url 'po-receive' order.id %}", {
@@ -115,8 +35,8 @@ $("#receive-order").click(function() {
secondary: [
{
field: 'location',
- label: 'New Location',
- title: 'Create new stock location',
+ label: '{% trans "New Location" %}',
+ title: '{% trans "Create new stock location" %}',
url: "{% url 'stock-location-create' %}",
},
]
@@ -133,7 +53,7 @@ $("#export-order").click(function() {
location.href = "{% url 'po-export' order.id %}";
});
-{% if order.status == OrderStatus.PENDING %}
+{% if order.status == PurchaseOrderStatus.PENDING %}
$('#new-po-line').click(function() {
launchModalForm("{% url 'po-line-item-create' %}",
{
@@ -144,8 +64,8 @@ $('#new-po-line').click(function() {
secondary: [
{
field: 'part',
- label: 'New Supplier Part',
- title: 'Create new supplier part',
+ label: '{% trans "New Supplier Part" %}',
+ title: '{% trans "Create new supplier part" %}',
url: "{% url 'supplier-part-create' %}",
data: {
supplier: {{ order.supplier.id }},
@@ -157,7 +77,153 @@ $('#new-po-line').click(function() {
});
{% endif %}
-$("#po-lines-table").inventreeTable({
+function reloadTable() {
+ $("#po-table").bootstrapTable("refresh");
+}
+
+function setupCallbacks() {
+ // Setup callbacks for the line buttons
+
+ var table = $("#po-table");
+
+ {% if order.status == PurchaseOrderStatus.PENDING %}
+ table.find(".button-line-edit").click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(`/order/purchase-order/line/${pk}/edit/`, {
+ success: reloadTable,
+ });
+ });
+
+ table.find(".button-line-delete").click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm(`/order/purchase-order/line/${pk}/delete/`, {
+ success: reloadTable,
+ });
+ });
+ {% endif %}
+
+ table.find(".button-line-receive").click(function() {
+ var pk = $(this).attr('pk');
+
+ launchModalForm("{% url 'po-receive' order.id %}", {
+ success: reloadTable,
+ data: {
+ line: pk,
+ },
+ secondary: [
+ {
+ field: 'location',
+ label: '{% trans "New Location" %}',
+ title: '{% trans "Create new stock location" %}',
+ url: "{% url 'stock-location-create' %}",
+ },
+ ]
+ });
+ });
+
+}
+
+$("#po-table").inventreeTable({
+ onPostBody: setupCallbacks,
+ formatNoMatches: function() { return "{% trans 'No line items found' %}"; },
+ queryParams: {
+ order: {{ order.id }},
+ part_detail: true,
+ },
+ url: "{% url 'api-po-line-list' %}",
+ columns: [
+ {
+ field: 'pk',
+ title: 'ID',
+ visible: false,
+ },
+ {
+ field: 'part',
+ sortable: true,
+ title: '{% trans "Part" %}',
+ formatter: function(value, row, index, field) {
+ if (row.part) {
+ return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`);
+ } else {
+ return '-';
+ }
+ },
+ },
+ {
+ sortable: true,
+ field: 'part_detail.description',
+ title: '{% trans "Description" %}',
+ },
+ {
+ sortable: true,
+ field: 'supplier_part_detail.SKU',
+ title: '{% trans "Order Code" %}',
+ formatter: function(value, row, index, field) {
+ return renderLink(value, `/supplier-part/${row.part}/`);
+ },
+ },
+ {
+ sortable: true,
+ field: 'reference',
+ title: '{% trans "Reference" %}',
+ },
+ {
+ sortable: true,
+ field: 'quantity',
+ title: '{% trans "Quantity" %}'
+ },
+ {
+ sortable: true,
+ field: 'received',
+ title: '{% trans "Received" %}',
+ formatter: function(value, row, index, field) {
+ return makeProgressBar(row.received, row.quantity, {
+ id: `order-line-progress-${row.pk}`,
+ });
+ },
+ sorter: function(valA, valB, rowA, rowB) {
+
+ if (rowA.received == 0 && rowB.received == 0) {
+ return (rowA.quantity > rowB.quantity) ? 1 : -1;
+ }
+
+ var progressA = parseFloat(rowA.received) / rowA.quantity;
+ var progressB = parseFloat(rowB.received) / rowB.quantity;
+
+ return (progressA < progressB) ? 1 : -1;
+ }
+ },
+ {
+ field: 'notes',
+ title: '{% trans "Notes" %}',
+ },
+ {
+ field: 'buttons',
+ title: '',
+ formatter: function(value, row, index, field) {
+ var html = ``;
+
+ var pk = row.pk;
+
+ {% if order.status == PurchaseOrderStatus.PENDING %}
+ html += makeIconButton('fa-edit', 'button-line-edit', pk, '{% trans "Edit line item" %}');
+ html += makeIconButton('fa-trash-alt icon-red', 'button-line-delete', pk, '{% trans "Delete line item" %}');
+ {% endif %}
+
+ {% if order.status == PurchaseOrderStatus.PLACED %}
+ if (row.received < row.quantity) {
+ html += makeIconButton('fa-clipboard-check', 'button-line-receive', pk, '{% trans "Receive line item" %}');
+ }
+ {% endif %}
+
+ html += `
`;
+
+ return html;
+ },
+ }
+ ]
});
diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html
index 9a8a061a79..385b669215 100644
--- a/InvenTree/order/templates/order/sales_order_base.html
+++ b/InvenTree/order/templates/order/sales_order_base.html
@@ -99,7 +99,7 @@ src="{% static 'img/blank_image.png' %}"
{{ order.shipment_date }}{{ order.shipped_by }} |
{% endif %}
- {% if order.status == OrderStatus.COMPLETE %}
+ {% if order.status == PurchaseOrderStatus.COMPLETE %}
|
{% trans "Received" %} |
diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html
index fb6feeab78..9d6aad6543 100644
--- a/InvenTree/order/templates/order/sales_order_detail.html
+++ b/InvenTree/order/templates/order/sales_order_detail.html
@@ -160,8 +160,8 @@ $("#so-lines-table").inventreeTable({
return (rowA.quantity > rowB.quantity) ? 1 : -1;
}
- var progressA = rowA.allocated / rowA.quantity;
- var progressB = rowB.allocated / rowA.quantity;
+ var progressA = parseFloat(rowA.allocated) / rowA.quantity;
+ var progressB = parseFloat(rowB.allocated) / rowB.quantity;
return (progressA < progressB) ? 1 : -1;
}
@@ -204,7 +204,7 @@ $("#so-lines-table").inventreeTable({
],
});
-function setupCallbacks(table) {
+function setupCallbacks() {
var table = $("#so-lines-table");
diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py
index 352d6c9546..26017a4e4e 100644
--- a/InvenTree/part/serializers.py
+++ b/InvenTree/part/serializers.py
@@ -52,14 +52,12 @@ class PartThumbSerializer(serializers.Serializer):
class PartBriefSerializer(InvenTreeModelSerializer):
""" Serializer for Part (brief detail) """
- url = serializers.CharField(source='get_absolute_url', read_only=True)
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
class Meta:
model = Part
fields = [
'pk',
- 'url',
'full_name',
'description',
'thumbnail',