2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-30 20:46:47 +00:00

Calculate 'fulfilled' quantity once a sales order is marked as shipped

- This allows us to delete the SalesOrderAllocation objects from the database
This commit is contained in:
Oliver Walters 2020-04-27 08:58:18 +10:00
parent 3c5ba75d27
commit 5167117067
10 changed files with 32 additions and 31 deletions

View File

@ -480,6 +480,8 @@ class BuildItem(models.Model):
self.stock_item = item self.stock_item = item
self.save() self.save()
# TODO - If the item__part object is not trackable, delete the stock item here
item.status = StockStatus.ASSIGNED_TO_BUILD item.status = StockStatus.ASSIGNED_TO_BUILD
item.build_order = self.build item.build_order = self.build
item.save() item.save()

View File

@ -348,14 +348,17 @@ class SOLineItemList(generics.ListCreateAPIView):
queryset = super().get_queryset(*args, **kwargs) queryset = super().get_queryset(*args, **kwargs)
return queryset.prefetch_related( queryset = queryset.prefetch_related(
'part', 'part',
'part__stock_items', 'part__stock_items',
'allocations', 'allocations',
'allocations__item__location', 'allocations__item__location',
'order', 'order',
'order__stock_items',
) )
return queryset
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend] filter_backends = [DjangoFilterBackend]

View File

@ -324,8 +324,11 @@ class SalesOrder(Order):
for allocation in line.allocations.all(): for allocation in line.allocations.all():
allocation.complete_allocation(user) allocation.complete_allocation(user)
# TODO - Remove the allocation from the database # Remove the allocation from the database once it has been 'fulfilled'
# allocation.delete() 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" # Ensure the order status is marked as "Shipped"
self.status = SalesOrderStatus.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}) 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): def allocated_quantity(self):
""" Return the total stock quantity allocated to this LineItem. """ Return the total stock quantity allocated to this LineItem.

View File

@ -211,6 +211,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
quantity = serializers.FloatField() quantity = serializers.FloatField()
allocated = serializers.FloatField(source='allocated_quantity', read_only=True) allocated = serializers.FloatField(source='allocated_quantity', read_only=True)
fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True)
class Meta: class Meta:
model = SalesOrderLineItem model = SalesOrderLineItem
@ -220,6 +221,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer):
'allocated', 'allocated',
'allocations', 'allocations',
'quantity', 'quantity',
'fulfilled',
'reference', 'reference',
'notes', 'notes',
'order', 'order',

View File

@ -5,7 +5,7 @@
{% include 'part/tabs.html' with tab='stock' %} {% include 'part/tabs.html' with tab='stock' %}
<h4>Part Stock</h4> <h4>{% trans "Part Stock" %}</h4>
<hr> <hr>
{% if part.is_template %} {% if part.is_template %}
@ -40,6 +40,7 @@
part: {{ part.id }}, part: {{ part.id }},
location_detail: true, location_detail: true,
part_detail: true, part_detail: true,
in_stock: true,
}, },
groupByField: 'location', groupByField: 'location',
buttons: [ buttons: [

View File

@ -48,7 +48,7 @@
<a href="{% url 'part-sales-orders' part.id %}">{% trans "Sales Orders" %} <span class='badge'>{{ part.sales_orders|length }}</span></a> <a href="{% url 'part-sales-orders' part.id %}">{% trans "Sales Orders" %} <span class='badge'>{{ part.sales_orders|length }}</span></a>
</li> </li>
{% endif %} {% endif %}
{% if part.trackable and 0 %} {% if part.trackable %}
<li{% ifequal tab 'track' %} class="active"{% endifequal %}> <li{% ifequal tab 'track' %} class="active"{% endifequal %}>
<a href="{% url 'part-track' part.id %}">{% trans "Tracking" %} <a href="{% url 'part-track' part.id %}">{% trans "Tracking" %}
{% if parts.serials.all|length > 0 %} {% if parts.serials.all|length > 0 %}

View File

@ -1,28 +1,12 @@
{% extends "part/part_base.html" %} {% extends "part/part_base.html" %}
{% load static %}
{% load i18n %}
{% block details %} {% block details %}
{% include 'part/tabs.html' with tab='track' %} {% include 'part/tabs.html' with tab='track' %}
Part tracking for {{ part.full_name }} <h4>{% trans "Part Tracking" %}</h4>
<table class="table table-striped"> <hr>
<tr>
<th>Serial</th>
<th>Status</th>
</tr>
{% for track in part.tracked_parts.all %}
<tr>
<td><a href="{% url 'track-detail' track.id %}">{{ track.serial }}</a></td>
<td>{{ track.get_status_display }}</td>
</tr>
{% endfor %}
</table>
<div class='container-fluid'>
<a href="{% url 'track-create' %}?part={{ part.id }}">
<button class="btn btn-success">New Tracked Part</button>
</a>
</div>
{% endblock %} {% endblock %}

View File

@ -58,7 +58,8 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
<a href="{% url 'build-detail' item.build_order.id %}"> <a href="{% url 'build-detail' item.build_order.id %}">
<div class='label label-large label-large-blue'>{% trans "Used in Build" %}</div> <div class='label label-large label-large-blue'>{% trans "Used in Build" %}</div>
</a> </a>
{% elif item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %} {% else %}
{% stock_status_label item.status large=True %}
{% endif %} {% endif %}
</h3> </h3>
<hr> <hr>

View File

@ -233,6 +233,7 @@
{% endif %} {% endif %}
part_detail: true, part_detail: true,
location_detail: true, location_detail: true,
in_stock: true,
}, },
url: "{% url 'api-stock-list' %}", url: "{% url 'api-stock-list' %}",
}); });

View File

@ -21,11 +21,6 @@ function getAvailableTableFilters(tableKey) {
title: '{% trans "Include sublocations" %}', title: '{% trans "Include sublocations" %}',
description: '{% trans "Include stock in sublocations" %}', description: '{% trans "Include stock in sublocations" %}',
}, },
in_stock: {
type: 'bool',
title: '{% trans "In stock" %}',
description: '{% trans "Item is in stock" %}',
},
active: { active: {
type: 'bool', type: 'bool',
title: '{% trans "Active parts" %}', title: '{% trans "Active parts" %}',