diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css
index bdfeb42f4c..e57188aa8c 100644
--- a/InvenTree/InvenTree/static/css/inventree.css
+++ b/InvenTree/InvenTree/static/css/inventree.css
@@ -336,6 +336,10 @@
padding-bottom: 2px;
};
+.panel-heading .badge {
+ float: right;
+}
+
.badge {
float: right;
background-color: #777;
diff --git a/InvenTree/InvenTree/static/script/inventree/stock.js b/InvenTree/InvenTree/static/script/inventree/stock.js
index e21971bb0f..3f1331dd2a 100644
--- a/InvenTree/InvenTree/static/script/inventree/stock.js
+++ b/InvenTree/InvenTree/static/script/inventree/stock.js
@@ -48,6 +48,7 @@ function loadStockTable(table, options) {
options.params['part_detail'] = true;
options.params['location_detail'] = true;
+ options.params['in_stock'] = true;
var params = options.params || {};
diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py
index 2eadd59138..647c5c49ec 100644
--- a/InvenTree/order/models.py
+++ b/InvenTree/order/models.py
@@ -315,10 +315,15 @@ class SalesOrder(Order):
def ship_order(self, user):
""" Mark this order as 'shipped' """
- return False
+ # The order can only be 'shipped' if the current status is PENDING
if not self.status == SalesOrderStatus.PENDING:
- return False
+ raise ValidationError({'status': _("SalesOrder cannot be shipped as it is not currently pending")})
+
+ # Complete the allocation for each allocated StockItem
+ for line in self.lines.all():
+ for allocation in line.allocations.all():
+ allocation.complete_allocation(user)
# Ensure the order status is marked as "Shipped"
self.status = SalesOrderStatus.SHIPPED
@@ -552,3 +557,30 @@ class SalesOrderAllocation(models.Model):
return self.item.location.pathstring
else:
return ""
+
+ def complete_allocation(self, user):
+ """
+ Complete this allocation (called when the parent SalesOrder is marked as "shipped"):
+
+ - Determine if the referenced StockItem needs to be "split" (if allocated quantity != stock quantity)
+ - Mark the StockItem as belonging to the Customer (this will remove it from stock)
+ """
+
+ item = self.item
+
+ # If the allocated quantity is less than the amount available,
+ # then split the stock item into two lots
+ if item.quantity > self.quantity:
+
+ # Grab a copy of the new stock item (which will keep track of its "parent")
+ item = item.splitStock(self.quantity, None, user)
+
+ # Assign the StockItem to the SalesOrder customer
+ item.customer = self.line.order.customer
+
+ # Clear the location
+ item.location = None
+
+ item.save()
+
+ print("Finalizing allocation for: " + str(self.item))
diff --git a/InvenTree/order/templates/order/sales_order_ship.html b/InvenTree/order/templates/order/sales_order_ship.html
index cb4a01f2f0..0060561e71 100644
--- a/InvenTree/order/templates/order/sales_order_ship.html
+++ b/InvenTree/order/templates/order/sales_order_ship.html
@@ -22,6 +22,8 @@
{% endif %}
+ {% trans "Sales Order" %} {{ order.reference }} - {{ order.customer.name }}
+
{% trans "Shipping this order means that the order will no longer be editable." %}
diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py
index eb0895f244..476a61b5e3 100644
--- a/InvenTree/order/views.py
+++ b/InvenTree/order/views.py
@@ -504,16 +504,14 @@ class SalesOrderShip(AjaxUpdateView):
context_object_name = 'order'
ajax_template_name = 'order/sales_order_ship.html'
ajax_form_title = _('Ship Order')
-
- def context_data(self):
- ctx = super().get_context_data()
- ctx['order'] = self.get_object()
-
- return ctx
def post(self, request, *args, **kwargs):
+ self.request = request
+
order = self.get_object()
+ self.object = order
+
form = self.get_form()
confirm = str2bool(request.POST.get('confirm', False))
@@ -534,7 +532,11 @@ class SalesOrderShip(AjaxUpdateView):
'form_valid': valid,
}
- return self.renderJsonResponse(request, form, data)
+ context = self.get_context_data()
+
+ context['order'] = order
+
+ return self.renderJsonResponse(request, form, data, context)
class PurchaseOrderExport(AjaxView):
diff --git a/InvenTree/stock/api.py b/InvenTree/stock/api.py
index 2c22665da0..9b7518580e 100644
--- a/InvenTree/stock/api.py
+++ b/InvenTree/stock/api.py
@@ -363,8 +363,17 @@ class StockList(generics.ListCreateAPIView):
# Start with all objects
stock_list = super().filter_queryset(queryset)
- # Filter out parts which are not actually "in stock"
- stock_list = stock_list.filter(customer=None, belongs_to=None)
+ in_stock = self.request.query_params.get('in_stock', None)
+
+ if in_stock is not None:
+ in_stock = str2bool(in_stock)
+
+ if in_stock:
+ # Filter out parts which are not actually "in stock"
+ stock_list = stock_list.filter(customer=None, belongs_to=None)
+ else:
+ # Only show parts which are not in stock
+ stock_list = stock_list.exclude(customer=None, belongs_to=None)
# Filter by 'allocated' patrs?
allocated = self.request.query_params.get('allocated', None)
@@ -418,7 +427,7 @@ class StockList(generics.ListCreateAPIView):
# Does the client wish to filter by stock location?
loc_id = self.request.query_params.get('location', None)
- cascade = str2bool(self.request.query_params.get('cascade', False))
+ cascade = str2bool(self.request.query_params.get('cascade', True))
if loc_id is not None:
diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py
index 4ea271c6ab..119b69de7c 100644
--- a/InvenTree/stock/models.py
+++ b/InvenTree/stock/models.py
@@ -634,6 +634,8 @@ class StockItem(MPTTModel):
# Remove the specified quantity from THIS stock item
self.take_stock(quantity, user, 'Split {n} items into new stock item'.format(n=quantity))
+ # Return a copy of the "new" stock item
+
@transaction.atomic
def move(self, location, notes, user, **kwargs):
""" Move part to a new location.
@@ -656,6 +658,9 @@ class StockItem(MPTTModel):
except InvalidOperation:
return False
+ if not self.in_stock:
+ raise ValidationError(_("StockItem cannot be moved as it is not in stock"))
+
if quantity <= 0:
return False
diff --git a/InvenTree/stock/templates/stock/item_base.html b/InvenTree/stock/templates/stock/item_base.html
index b6114927f0..77023eba35 100644
--- a/InvenTree/stock/templates/stock/item_base.html
+++ b/InvenTree/stock/templates/stock/item_base.html
@@ -15,6 +15,12 @@ InvenTree | {% trans "Stock Item" %} - {{ item }}
{% block pre_content %}
{% include 'stock/loc_link.html' with location=item.location %}
+{% if item.customer %}
+
+{% endif %}
+
{% for allocation in item.sales_order_allocations.all %}