From 51671170679e6aaefd84f55302fb6809cb8d9197 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 27 Apr 2020 08:58:18 +1000 Subject: [PATCH] Calculate 'fulfilled' quantity once a sales order is marked as shipped - This allows us to delete the SalesOrderAllocation objects from the database --- InvenTree/build/models.py | 2 ++ InvenTree/order/api.py | 5 +++- InvenTree/order/models.py | 16 +++++++++++-- InvenTree/order/serializers.py | 2 ++ InvenTree/part/templates/part/stock.html | 3 ++- InvenTree/part/templates/part/tabs.html | 2 +- InvenTree/part/templates/part/track.html | 24 ++++--------------- .../stock/templates/stock/item_base.html | 3 ++- InvenTree/stock/templates/stock/location.html | 1 + InvenTree/templates/table_filters.html | 5 ---- 10 files changed, 32 insertions(+), 31 deletions(-) diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 11694299af..6bc6160af6 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -480,6 +480,8 @@ class BuildItem(models.Model): self.stock_item = item self.save() + # TODO - If the item__part object is not trackable, delete the stock item here + item.status = StockStatus.ASSIGNED_TO_BUILD item.build_order = self.build item.save() diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index d8568ddf3b..0f84d6bc32 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -348,14 +348,17 @@ class SOLineItemList(generics.ListCreateAPIView): queryset = super().get_queryset(*args, **kwargs) - return queryset.prefetch_related( + queryset = queryset.prefetch_related( 'part', 'part__stock_items', 'allocations', 'allocations__item__location', 'order', + 'order__stock_items', ) + return queryset + permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend] diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index a4501bd9eb..a7a176a7bf 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -324,8 +324,11 @@ class SalesOrder(Order): for allocation in line.allocations.all(): allocation.complete_allocation(user) - # TODO - Remove the allocation from the database - # allocation.delete() + # Remove the allocation from the database once it has been 'fulfilled' + if allocation.item.sales_order == self.order: + allocation.delete() + else: + raise ValidationError("Could not complete order - allocation item not fulfilled") # Ensure the order status is marked as "Shipped" self.status = SalesOrderStatus.SHIPPED @@ -458,6 +461,15 @@ class SalesOrderLineItem(OrderLineItem): part = models.ForeignKey('part.Part', on_delete=models.SET_NULL, related_name='sales_order_line_items', null=True, help_text=_('Part'), limit_choices_to={'salable': True}) + def fulfilled_quantity(self): + """ + Return the total stock quantity fulfilled against this line item. + """ + + query = self.order.stock_items.filter(part=self.part).aggregate(fulfilled=Coalesce(Sum('quantity'), Decimal(0))) + + return query['fulfilled'] + def allocated_quantity(self): """ Return the total stock quantity allocated to this LineItem. diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 95ebae34da..0738b9dfbe 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -211,6 +211,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer): quantity = serializers.FloatField() allocated = serializers.FloatField(source='allocated_quantity', read_only=True) + fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True) class Meta: model = SalesOrderLineItem @@ -220,6 +221,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer): 'allocated', 'allocations', 'quantity', + 'fulfilled', 'reference', 'notes', 'order', diff --git a/InvenTree/part/templates/part/stock.html b/InvenTree/part/templates/part/stock.html index a692b11b01..03174f45c3 100644 --- a/InvenTree/part/templates/part/stock.html +++ b/InvenTree/part/templates/part/stock.html @@ -5,7 +5,7 @@ {% include 'part/tabs.html' with tab='stock' %} -

Part Stock

+

{% trans "Part Stock" %}


{% if part.is_template %} @@ -40,6 +40,7 @@ part: {{ part.id }}, location_detail: true, part_detail: true, + in_stock: true, }, groupByField: 'location', buttons: [ diff --git a/InvenTree/part/templates/part/tabs.html b/InvenTree/part/templates/part/tabs.html index e092708a0a..283afcd2ae 100644 --- a/InvenTree/part/templates/part/tabs.html +++ b/InvenTree/part/templates/part/tabs.html @@ -48,7 +48,7 @@ {% trans "Sales Orders" %} {{ part.sales_orders|length }} {% endif %} - {% if part.trackable and 0 %} + {% if part.trackable %} {% trans "Tracking" %} {% if parts.serials.all|length > 0 %} diff --git a/InvenTree/part/templates/part/track.html b/InvenTree/part/templates/part/track.html index 9196715437..e3c47e4777 100644 --- a/InvenTree/part/templates/part/track.html +++ b/InvenTree/part/templates/part/track.html @@ -1,28 +1,12 @@ {% extends "part/part_base.html" %} - +{% load static %} +{% load i18n %} {% block details %} {% include 'part/tabs.html' with tab='track' %} -Part tracking for {{ part.full_name }} +

{% trans "Part Tracking" %}

- - - - - -{% for track in part.tracked_parts.all %} - - - - -{% endfor %} -
SerialStatus
{{ track.serial }}{{ track.get_status_display }}
- -
+
{% endblock %} \ No newline at end of file diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html index 25f582a27c..8428418392 100644 --- a/InvenTree/stock/templates/stock/item_base.html +++ b/InvenTree/stock/templates/stock/item_base.html @@ -58,7 +58,8 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
{% trans "Used in Build" %}
- {% elif item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %} + {% else %} + {% stock_status_label item.status large=True %} {% endif %}
diff --git a/InvenTree/stock/templates/stock/location.html b/InvenTree/stock/templates/stock/location.html index f7b5a7a70d..521e73bf7e 100644 --- a/InvenTree/stock/templates/stock/location.html +++ b/InvenTree/stock/templates/stock/location.html @@ -233,6 +233,7 @@ {% endif %} part_detail: true, location_detail: true, + in_stock: true, }, url: "{% url 'api-stock-list' %}", }); diff --git a/InvenTree/templates/table_filters.html b/InvenTree/templates/table_filters.html index a8a2fddcdf..b337b25ac8 100644 --- a/InvenTree/templates/table_filters.html +++ b/InvenTree/templates/table_filters.html @@ -21,11 +21,6 @@ function getAvailableTableFilters(tableKey) { title: '{% trans "Include sublocations" %}', description: '{% trans "Include stock in sublocations" %}', }, - in_stock: { - type: 'bool', - title: '{% trans "In stock" %}', - description: '{% trans "Item is in stock" %}', - }, active: { type: 'bool', title: '{% trans "Active parts" %}',