mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 12:36:45 +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:
parent
3c5ba75d27
commit
5167117067
@ -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()
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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',
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
{% include 'part/tabs.html' with tab='stock' %}
|
||||
|
||||
<h4>Part Stock</h4>
|
||||
<h4>{% trans "Part Stock" %}</h4>
|
||||
<hr>
|
||||
|
||||
{% if part.is_template %}
|
||||
@ -40,6 +40,7 @@
|
||||
part: {{ part.id }},
|
||||
location_detail: true,
|
||||
part_detail: true,
|
||||
in_stock: true,
|
||||
},
|
||||
groupByField: 'location',
|
||||
buttons: [
|
||||
|
@ -48,7 +48,7 @@
|
||||
<a href="{% url 'part-sales-orders' part.id %}">{% trans "Sales Orders" %} <span class='badge'>{{ part.sales_orders|length }}</span></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if part.trackable and 0 %}
|
||||
{% if part.trackable %}
|
||||
<li{% ifequal tab 'track' %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'part-track' part.id %}">{% trans "Tracking" %}
|
||||
{% if parts.serials.all|length > 0 %}
|
||||
|
@ -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 }}
|
||||
<h4>{% trans "Part Tracking" %}</h4>
|
||||
|
||||
<table class="table table-striped">
|
||||
<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>
|
||||
<hr>
|
||||
|
||||
{% endblock %}
|
@ -58,7 +58,8 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
|
||||
<a href="{% url 'build-detail' item.build_order.id %}">
|
||||
<div class='label label-large label-large-blue'>{% trans "Used in Build" %}</div>
|
||||
</a>
|
||||
{% elif item.status in StockStatus.UNAVAILABLE_CODES %}{% stock_status_label item.status large=True %}
|
||||
{% else %}
|
||||
{% stock_status_label item.status large=True %}
|
||||
{% endif %}
|
||||
</h3>
|
||||
<hr>
|
||||
|
@ -233,6 +233,7 @@
|
||||
{% endif %}
|
||||
part_detail: true,
|
||||
location_detail: true,
|
||||
in_stock: true,
|
||||
},
|
||||
url: "{% url 'api-stock-list' %}",
|
||||
});
|
||||
|
@ -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" %}',
|
||||
|
Loading…
x
Reference in New Issue
Block a user