mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +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:
		@@ -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()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: [
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 %}
 | 
				
			||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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' %}",
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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" %}',
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user