mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 12:35:46 +00:00
More stuffs:
- Allow filtering of salesorderlineitem by "completed" status - Allow deletion of (empty) shipment - Show which items are going to be shipped
This commit is contained in:
@ -551,6 +551,39 @@ class SODetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
return queryset
|
||||
|
||||
|
||||
class SOLineItemFilter(rest_filters.FilterSet):
|
||||
"""
|
||||
Custom filters for SOLineItemList endpoint
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = models.SalesOrderLineItem
|
||||
fields = [
|
||||
'order',
|
||||
'part',
|
||||
]
|
||||
|
||||
completed = rest_filters.BooleanFilter(label='completed', method='filter_completed')
|
||||
|
||||
def filter_completed(self, queryset, name, value):
|
||||
"""
|
||||
Filter by lines which are "completed"
|
||||
|
||||
A line is completed when shipped >= quantity
|
||||
"""
|
||||
|
||||
value = str2bool(value)
|
||||
|
||||
q = Q(shipped__gte=F('quantity'))
|
||||
|
||||
if value:
|
||||
queryset = queryset.filter(q)
|
||||
else:
|
||||
queryset = queryset.exclude(q)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class SOLineItemList(generics.ListCreateAPIView):
|
||||
"""
|
||||
API endpoint for accessing a list of SalesOrderLineItem objects.
|
||||
@ -558,6 +591,7 @@ class SOLineItemList(generics.ListCreateAPIView):
|
||||
|
||||
queryset = models.SalesOrderLineItem.objects.all()
|
||||
serializer_class = serializers.SOLineItemSerializer
|
||||
filterset_class = SOLineItemFilter
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
@ -620,6 +654,28 @@ class SOLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = serializers.SOLineItemSerializer
|
||||
|
||||
|
||||
class SalesOrderComplete(generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint for manually marking a SalesOrder as "complete".
|
||||
"""
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
serializer_class = serializers.SalesOrderShipmentCompleteSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
||||
ctx = super().get_serializer_context()
|
||||
|
||||
ctx['request'] = self.request
|
||||
|
||||
try:
|
||||
ctx['order'] = models.SalesOrder.objects.get(pk=self.kwargs.get('pk', None))
|
||||
except:
|
||||
pass
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class SalesOrderAllocate(generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to allocate stock items against a SalesOrder
|
||||
@ -758,7 +814,7 @@ class SOShipmentList(generics.ListCreateAPIView):
|
||||
]
|
||||
|
||||
|
||||
class SOShipmentDetail(generics.RetrieveUpdateAPIView):
|
||||
class SOShipmentDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API detail endpooint for SalesOrderShipment model
|
||||
"""
|
||||
@ -863,6 +919,7 @@ order_api_urls = [
|
||||
|
||||
# Sales order detail view
|
||||
url(r'^(?P<pk>\d+)/', include([
|
||||
url(r'^complete/', SalesOrderComplete.as_view(), name='api-so-complete'),
|
||||
url(r'^allocate/', SalesOrderAllocate.as_view(), name='api-so-allocate'),
|
||||
url(r'^.*$', SODetail.as_view(), name='api-so-detail'),
|
||||
])),
|
||||
|
@ -363,11 +363,30 @@ class PurchaseOrder(Order):
|
||||
|
||||
return self.lines.filter(quantity__gt=F('received'))
|
||||
|
||||
def completed_line_items(self):
|
||||
"""
|
||||
Return a list of completed line items against this order
|
||||
"""
|
||||
return self.lines.filter(quantity__lte=F('received'))
|
||||
|
||||
@property
|
||||
def line_count(self):
|
||||
return self.lines.count()
|
||||
|
||||
@property
|
||||
def completed_line_count(self):
|
||||
|
||||
return self.completed_line_items().count()
|
||||
|
||||
@property
|
||||
def pending_line_count(self):
|
||||
return self.pending_line_items().count()
|
||||
|
||||
@property
|
||||
def is_complete(self):
|
||||
""" Return True if all line items have been received """
|
||||
|
||||
return self.pending_line_items().count() == 0
|
||||
return self.lines.count() > 0 and self.pending_line_items().count() == 0
|
||||
|
||||
@transaction.atomic
|
||||
def receive_line_item(self, line, location, quantity, user, status=StockStatus.OK, purchase_price=None, **kwargs):
|
||||
@ -601,8 +620,7 @@ class SalesOrder(Order):
|
||||
and mark it as "shipped" if so.
|
||||
"""
|
||||
|
||||
return all([line.is_completed() for line in self.lines.all()])
|
||||
|
||||
return self.lines.count() > 0 and all([line.is_completed() for line in self.lines.all()])
|
||||
|
||||
def can_cancel(self):
|
||||
"""
|
||||
@ -635,6 +653,30 @@ class SalesOrder(Order):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def line_count(self):
|
||||
return self.lines.count()
|
||||
|
||||
def completed_line_items(self):
|
||||
"""
|
||||
Return a queryset of the completed line items for this order
|
||||
"""
|
||||
return self.lines.filter(shipped__gte=F('quantity'))
|
||||
|
||||
def pending_line_items(self):
|
||||
"""
|
||||
Return a queryset of the pending line items for this order
|
||||
"""
|
||||
return self.lines.filter(shipped__lt=F('quantity'))
|
||||
|
||||
@property
|
||||
def completed_line_count(self):
|
||||
return self.completed_line_items().count()
|
||||
|
||||
@property
|
||||
def pending_line_count(self):
|
||||
return self.pending_line_items().count()
|
||||
|
||||
|
||||
class PurchaseOrderAttachment(InvenTreeAttachment):
|
||||
"""
|
||||
|
@ -489,6 +489,8 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
||||
item_detail = stock.serializers.StockItemSerializer(source='item', many=False, read_only=True)
|
||||
location_detail = stock.serializers.LocationSerializer(source='item.location', many=False, read_only=True)
|
||||
|
||||
shipment_date = serializers.DateField(source='shipment.shipment_date', read_only=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
order_detail = kwargs.pop('order_detail', False)
|
||||
@ -527,6 +529,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
||||
'part',
|
||||
'part_detail',
|
||||
'shipment',
|
||||
'shipment_date',
|
||||
]
|
||||
|
||||
|
||||
@ -734,6 +737,20 @@ class SOShipmentAllocationItemSerializer(serializers.Serializer):
|
||||
return data
|
||||
|
||||
|
||||
class SalesOrderCompleteSerializer(serializers.Serializer):
|
||||
"""
|
||||
DRF serializer for manually marking a sales order as complete
|
||||
"""
|
||||
|
||||
def save(self):
|
||||
|
||||
request = self.context['request']
|
||||
order = self.context['order']
|
||||
data = self.validated_data
|
||||
|
||||
|
||||
|
||||
|
||||
class SOShipmentAllocationSerializer(serializers.Serializer):
|
||||
"""
|
||||
DRF serializer for allocation of stock items against a sales order / shipment
|
||||
|
@ -119,6 +119,18 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<td>{{ order.supplier_reference }}{% include "clip.html"%}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-tasks'></span></td>
|
||||
<td>{% trans "Completed Line Items" %}</td>
|
||||
<td>
|
||||
{{ order.completed_line_count }} / {{ order.line_count }}
|
||||
{% if order.is_complete %}
|
||||
<span class='badge bg-success badge-right rounded-pill'>{% trans "Complete" %}</span>
|
||||
{% else %}
|
||||
<span class='badge bg-danger badge-right rounded-pill'>{% trans "Incomplete" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if order.link %}
|
||||
<tr>
|
||||
<td><span class='fas fa-link'></span></td>
|
||||
|
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
<div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
|
||||
{% include "filter_list.html" with id="order-lines" %}
|
||||
{% include "filter_list.html" with id="purchase-order-lines" %}
|
||||
</div>
|
||||
|
||||
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
|
||||
@ -190,6 +190,10 @@ $('#new-po-line').click(function() {
|
||||
$('#receive-selected-items').click(function() {
|
||||
var items = $("#po-line-table").bootstrapTable('getSelections');
|
||||
|
||||
if (items.length == 0) {
|
||||
items = $("#po-line-table").bootstrapTable('getData');
|
||||
}
|
||||
|
||||
receivePurchaseOrderItems(
|
||||
{{ order.id }},
|
||||
items,
|
||||
|
@ -63,8 +63,8 @@ src="{% static 'img/blank_image.png' %}"
|
||||
|
||||
</div>
|
||||
{% if order.status == SalesOrderStatus.PENDING %}
|
||||
<button type='button' class='btn btn-success' id='ship-order' title='{% trans "Ship Order" %}'>
|
||||
<span class='fas fa-truck'></span> {% trans "Ship Order" %}
|
||||
<button type='button' class='btn btn-success' id='complete-order' title='{% trans "Complete Sales Order" %}'>
|
||||
<span class='fas fa-check-circle'></span> {% trans "Complete Order" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@ -123,6 +123,18 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<td>{{ order.customer_reference }}{% include "clip.html"%}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-tasks'></span></td>
|
||||
<td>{% trans "Completed Line Items" %}</td>
|
||||
<td>
|
||||
{{ order.completed_line_count }} / {{ order.line_count }}
|
||||
{% if order.is_completed %}
|
||||
<span class='badge bg-success badge-right rounded-pill'>{% trans "Complete" %}</span>
|
||||
{% else %}
|
||||
<span class='badge bg-danger badge-right rounded-pill'>{% trans "Incomplete" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if order.link %}
|
||||
<tr>
|
||||
<td><span class='fas fa-link'></span></td>
|
||||
@ -149,13 +161,6 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<td>{{ order.shipment_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.shipped_by }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order.status == PurchaseOrderStatus.COMPLETE %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Received" %}</td>
|
||||
<td>{{ order.complete_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.received_by }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order.responsible %}
|
||||
<tr>
|
||||
<td><span class='fas fa-users'></span></td>
|
||||
@ -203,10 +208,8 @@ $("#cancel-order").click(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("#ship-order").click(function() {
|
||||
launchModalForm("{% url 'so-ship' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
$("#complete-order").click(function() {
|
||||
completeSalesOrder({{ order.pk }});
|
||||
});
|
||||
|
||||
{% if report_enabled %}
|
||||
|
Reference in New Issue
Block a user