mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +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:
parent
e74e7138a9
commit
e1668c8662
@ -551,6 +551,39 @@ class SODetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
return queryset
|
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):
|
class SOLineItemList(generics.ListCreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint for accessing a list of SalesOrderLineItem objects.
|
API endpoint for accessing a list of SalesOrderLineItem objects.
|
||||||
@ -558,6 +591,7 @@ class SOLineItemList(generics.ListCreateAPIView):
|
|||||||
|
|
||||||
queryset = models.SalesOrderLineItem.objects.all()
|
queryset = models.SalesOrderLineItem.objects.all()
|
||||||
serializer_class = serializers.SOLineItemSerializer
|
serializer_class = serializers.SOLineItemSerializer
|
||||||
|
filterset_class = SOLineItemFilter
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
|
|
||||||
@ -620,6 +654,28 @@ class SOLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
serializer_class = serializers.SOLineItemSerializer
|
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):
|
class SalesOrderAllocate(generics.CreateAPIView):
|
||||||
"""
|
"""
|
||||||
API endpoint to allocate stock items against a SalesOrder
|
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
|
API detail endpooint for SalesOrderShipment model
|
||||||
"""
|
"""
|
||||||
@ -863,6 +919,7 @@ order_api_urls = [
|
|||||||
|
|
||||||
# Sales order detail view
|
# Sales order detail view
|
||||||
url(r'^(?P<pk>\d+)/', include([
|
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'^allocate/', SalesOrderAllocate.as_view(), name='api-so-allocate'),
|
||||||
url(r'^.*$', SODetail.as_view(), name='api-so-detail'),
|
url(r'^.*$', SODetail.as_view(), name='api-so-detail'),
|
||||||
])),
|
])),
|
||||||
|
@ -363,11 +363,30 @@ class PurchaseOrder(Order):
|
|||||||
|
|
||||||
return self.lines.filter(quantity__gt=F('received'))
|
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
|
@property
|
||||||
def is_complete(self):
|
def is_complete(self):
|
||||||
""" Return True if all line items have been received """
|
""" 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
|
@transaction.atomic
|
||||||
def receive_line_item(self, line, location, quantity, user, status=StockStatus.OK, purchase_price=None, **kwargs):
|
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.
|
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):
|
def can_cancel(self):
|
||||||
"""
|
"""
|
||||||
@ -635,6 +653,30 @@ class SalesOrder(Order):
|
|||||||
|
|
||||||
return True
|
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):
|
class PurchaseOrderAttachment(InvenTreeAttachment):
|
||||||
"""
|
"""
|
||||||
|
@ -489,6 +489,8 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
|||||||
item_detail = stock.serializers.StockItemSerializer(source='item', many=False, read_only=True)
|
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)
|
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):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
order_detail = kwargs.pop('order_detail', False)
|
order_detail = kwargs.pop('order_detail', False)
|
||||||
@ -527,6 +529,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
|||||||
'part',
|
'part',
|
||||||
'part_detail',
|
'part_detail',
|
||||||
'shipment',
|
'shipment',
|
||||||
|
'shipment_date',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -734,6 +737,20 @@ class SOShipmentAllocationItemSerializer(serializers.Serializer):
|
|||||||
return data
|
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):
|
class SOShipmentAllocationSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
DRF serializer for allocation of stock items against a sales order / shipment
|
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>
|
<td>{{ order.supplier_reference }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if order.link %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-link'></span></td>
|
<td><span class='fas fa-link'></span></td>
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class='panel-content'>
|
<div class='panel-content'>
|
||||||
<div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
|
<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>
|
</div>
|
||||||
|
|
||||||
<table class='table table-striped table-condensed' id='po-line-table' data-toolbar='#order-toolbar-buttons'>
|
<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() {
|
$('#receive-selected-items').click(function() {
|
||||||
var items = $("#po-line-table").bootstrapTable('getSelections');
|
var items = $("#po-line-table").bootstrapTable('getSelections');
|
||||||
|
|
||||||
|
if (items.length == 0) {
|
||||||
|
items = $("#po-line-table").bootstrapTable('getData');
|
||||||
|
}
|
||||||
|
|
||||||
receivePurchaseOrderItems(
|
receivePurchaseOrderItems(
|
||||||
{{ order.id }},
|
{{ order.id }},
|
||||||
items,
|
items,
|
||||||
|
@ -63,8 +63,8 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% if order.status == SalesOrderStatus.PENDING %}
|
{% if order.status == SalesOrderStatus.PENDING %}
|
||||||
<button type='button' class='btn btn-success' id='ship-order' title='{% trans "Ship Order" %}'>
|
<button type='button' class='btn btn-success' id='complete-order' title='{% trans "Complete Sales Order" %}'>
|
||||||
<span class='fas fa-truck'></span> {% trans "Ship Order" %}
|
<span class='fas fa-check-circle'></span> {% trans "Complete Order" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -123,6 +123,18 @@ src="{% static 'img/blank_image.png' %}"
|
|||||||
<td>{{ order.customer_reference }}{% include "clip.html"%}</td>
|
<td>{{ order.customer_reference }}{% include "clip.html"%}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if order.link %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-link'></span></td>
|
<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>
|
<td>{{ order.shipment_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.shipped_by }}</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% 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 %}
|
{% if order.responsible %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class='fas fa-users'></span></td>
|
<td><span class='fas fa-users'></span></td>
|
||||||
@ -203,10 +208,8 @@ $("#cancel-order").click(function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#ship-order").click(function() {
|
$("#complete-order").click(function() {
|
||||||
launchModalForm("{% url 'so-ship' order.id %}", {
|
completeSalesOrder({{ order.pk }});
|
||||||
reload: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if report_enabled %}
|
{% if report_enabled %}
|
||||||
|
@ -1413,6 +1413,7 @@ function allocateStockToBuild(build_id, part_id, bom_items, options={}) {
|
|||||||
filters: {
|
filters: {
|
||||||
bom_item: bom_item.pk,
|
bom_item: bom_item.pk,
|
||||||
in_stock: true,
|
in_stock: true,
|
||||||
|
available: true,
|
||||||
part_detail: true,
|
part_detail: true,
|
||||||
location_detail: true,
|
location_detail: true,
|
||||||
},
|
},
|
||||||
|
@ -41,6 +41,9 @@ function salesOrderShipmentFields(options={}) {
|
|||||||
var fields = {
|
var fields = {
|
||||||
order: {},
|
order: {},
|
||||||
reference: {},
|
reference: {},
|
||||||
|
tracking_number: {
|
||||||
|
icon: 'fa-hashtag',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// If order is specified, hide the order field
|
// If order is specified, hide the order field
|
||||||
@ -58,19 +61,75 @@ function salesOrderShipmentFields(options={}) {
|
|||||||
*/
|
*/
|
||||||
function completeShipment(shipment_id) {
|
function completeShipment(shipment_id) {
|
||||||
|
|
||||||
constructForm(`/api/order/so/shipment/${shipment_id}/ship/`, {
|
// Request the list of stock items which will be shipped
|
||||||
method: 'POST',
|
inventreeGet(`/api/order/so/shipment/${shipment_id}/`, {}, {
|
||||||
title: '{% trans "Complete Shipment" %}',
|
success: function(shipment) {
|
||||||
fields: {
|
var allocations = shipment.allocations;
|
||||||
tracking_number: {},
|
|
||||||
},
|
var html = '';
|
||||||
confirm: true,
|
|
||||||
confirmMessage: '{% trans "Confirm Shipment" %}',
|
if (!allocations || allocations.length == 0) {
|
||||||
onSuccess: function(data) {
|
html = `
|
||||||
// Reload tables
|
<div class='alert alert-block alert-danger'>
|
||||||
$('#so-lines-table').bootstrapTable('refresh');
|
{% trans "No stock items have been allocated to this shipment" %}
|
||||||
$('#pending-shipments-table').bootstrapTable('refresh');
|
</div>
|
||||||
$('#completed-shipments-table').bootstrapTable('refresh');
|
`;
|
||||||
|
} else {
|
||||||
|
html = `
|
||||||
|
{% trans "The following stock items will be shipped" %}
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Part" %}</th>
|
||||||
|
<th>{% trans "Stock Item" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
allocations.forEach(function(allocation) {
|
||||||
|
|
||||||
|
var part = allocation.part_detail;
|
||||||
|
var thumb = thumbnailImage(part.thumbnail || part.image);
|
||||||
|
|
||||||
|
var stock = '';
|
||||||
|
|
||||||
|
if (allocation.serial) {
|
||||||
|
stock = `{% trans "Serial Number" %}: ${allocation.serial}`;
|
||||||
|
} else {
|
||||||
|
stock = `{% trans "Quantity" %}: ${allocation.quantity}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td>${thumb} ${part.full_name}</td>
|
||||||
|
<td>${stock}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructForm(`/api/order/so/shipment/${shipment_id}/ship/`, {
|
||||||
|
method: 'POST',
|
||||||
|
title: '{% trans "Complete Shipment" %}',
|
||||||
|
fields: {
|
||||||
|
tracking_number: {},
|
||||||
|
},
|
||||||
|
preFormContent: html,
|
||||||
|
confirm: true,
|
||||||
|
confirmMessage: '{% trans "Confirm Shipment" %}',
|
||||||
|
onSuccess: function(data) {
|
||||||
|
// Reload tables
|
||||||
|
$('#so-lines-table').bootstrapTable('refresh');
|
||||||
|
$('#pending-shipments-table').bootstrapTable('refresh');
|
||||||
|
$('#completed-shipments-table').bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -393,7 +452,9 @@ function newPurchaseOrderFromOrderWizard(e) {
|
|||||||
*/
|
*/
|
||||||
function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
function receivePurchaseOrderItems(order_id, line_items, options={}) {
|
||||||
|
|
||||||
|
// Zero items selected?
|
||||||
if (line_items.length == 0) {
|
if (line_items.length == 0) {
|
||||||
|
|
||||||
showAlertDialog(
|
showAlertDialog(
|
||||||
'{% trans "Select Line Items" %}',
|
'{% trans "Select Line Items" %}',
|
||||||
'{% trans "At least one line item must be selected" %}',
|
'{% trans "At least one line item must be selected" %}',
|
||||||
@ -1256,6 +1317,10 @@ function loadSalesOrderShipmentTable(table, options={}) {
|
|||||||
html += makeIconButton('fa-truck icon-green', 'button-shipment-ship', pk, '{% trans "Complete shipment" %}');
|
html += makeIconButton('fa-truck icon-green', 'button-shipment-ship', pk, '{% trans "Complete shipment" %}');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var enable_delete = row.allocations && row.allocations.length == 0;
|
||||||
|
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-shipment-delete', pk, '{% trans "Delete shipment" %}', {disabled: !enable_delete});
|
||||||
|
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
@ -1268,10 +1333,12 @@ function loadSalesOrderShipmentTable(table, options={}) {
|
|||||||
$(table).find('.button-shipment-edit').click(function() {
|
$(table).find('.button-shipment-edit').click(function() {
|
||||||
var pk = $(this).attr('pk');
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
var fields = salesOrderShipmentFields();
|
||||||
|
|
||||||
|
delete fields.order;
|
||||||
|
|
||||||
constructForm(`/api/order/so/shipment/${pk}/`, {
|
constructForm(`/api/order/so/shipment/${pk}/`, {
|
||||||
fields: {
|
fields: fields,
|
||||||
reference: {},
|
|
||||||
},
|
|
||||||
title: '{% trans "Edit Shipment" %}',
|
title: '{% trans "Edit Shipment" %}',
|
||||||
onSuccess: function() {
|
onSuccess: function() {
|
||||||
$(table).bootstrapTable('refresh');
|
$(table).bootstrapTable('refresh');
|
||||||
@ -1284,6 +1351,18 @@ function loadSalesOrderShipmentTable(table, options={}) {
|
|||||||
|
|
||||||
completeShipment(pk);
|
completeShipment(pk);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(table).find('.button-shipment-delete').click(function() {
|
||||||
|
var pk = $(this).attr('pk');
|
||||||
|
|
||||||
|
constructForm(`/api/order/so/shipment/${pk}/`, {
|
||||||
|
title: '{% trans "Delete Shipment" %}',
|
||||||
|
method: 'DELETE',
|
||||||
|
onSuccess: function() {
|
||||||
|
$(table).bootstrapTable('refresh');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$(table).inventreeTable({
|
$(table).inventreeTable({
|
||||||
@ -1510,14 +1589,6 @@ function allocateStockToSalesOrder(order_id, line_items, options={}) {
|
|||||||
},
|
},
|
||||||
value: options.shipment || null,
|
value: options.shipment || null,
|
||||||
auto_fill: true,
|
auto_fill: true,
|
||||||
secondary: {
|
|
||||||
title: '{% trans "New Shipment" %}',
|
|
||||||
fields: function() {
|
|
||||||
return salesOrderShipmentFields({
|
|
||||||
order: order_id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preFormContent: html,
|
preFormContent: html,
|
||||||
@ -1854,7 +1925,7 @@ function showAllocationSubTable(index, row, element, options) {
|
|||||||
table.bootstrapTable({
|
table.bootstrapTable({
|
||||||
onPostBody: setupCallbacks,
|
onPostBody: setupCallbacks,
|
||||||
data: row.allocations,
|
data: row.allocations,
|
||||||
showHeader: false,
|
showHeader: true,
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
field: 'part_detail',
|
field: 'part_detail',
|
||||||
@ -1865,7 +1936,7 @@ function showAllocationSubTable(index, row, element, options) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'allocated',
|
field: 'allocated',
|
||||||
title: '{% trans "Quantity" %}',
|
title: '{% trans "Stock Item" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
var text = '';
|
var text = '';
|
||||||
|
|
||||||
@ -1883,8 +1954,8 @@ function showAllocationSubTable(index, row, element, options) {
|
|||||||
title: '{% trans "Location" %}',
|
title: '{% trans "Location" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
if (shipped) {
|
if (row.shipment_date) {
|
||||||
return `<em>{% trans "Shipped to customer" %}</em>`;
|
return `<em>{% trans "Shipped to customer" %} - ${row.shipment_date}</em>`;
|
||||||
} else if (row.location) {
|
} else if (row.location) {
|
||||||
// Location specified
|
// Location specified
|
||||||
return renderLink(
|
return renderLink(
|
||||||
@ -1896,21 +1967,17 @@ function showAllocationSubTable(index, row, element, options) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// TODO: ?? What is 'po' field all about?
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
field: 'po'
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
field: 'buttons',
|
field: 'buttons',
|
||||||
title: '{% trans "Actions" %}',
|
title: '{% trans "" %}',
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
|
|
||||||
var html = `<div class='btn-group float-right' role='group'>`;
|
var html = `<div class='btn-group float-right' role='group'>`;
|
||||||
var pk = row.pk;
|
var pk = row.pk;
|
||||||
|
|
||||||
if (!shipped) {
|
if (row.shipment_date) {
|
||||||
|
html += `<span class='badge bg-success badge-right'>{% trans "Shipped" %}</span>`;
|
||||||
|
} else {
|
||||||
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
|
html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}');
|
||||||
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
|
html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}');
|
||||||
}
|
}
|
||||||
@ -2017,7 +2084,7 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
|
|
||||||
var filter_target = options.filter_target || '#filter-list-sales-order-lines';
|
var filter_target = options.filter_target || '#filter-list-sales-order-lines';
|
||||||
|
|
||||||
setupFilterList('salesorderlineitems', $(table), filter_target);
|
setupFilterList('salesorderlineitem', $(table), filter_target);
|
||||||
|
|
||||||
// Is the order pending?
|
// Is the order pending?
|
||||||
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
var pending = options.status == {{ SalesOrderStatus.PENDING }};
|
||||||
@ -2228,7 +2295,21 @@ function loadSalesOrderLineItemTable(table, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
|
html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}');
|
||||||
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line item " %}');
|
|
||||||
|
var delete_disabled = false;
|
||||||
|
|
||||||
|
var title = '{% trans "Delete line item" %}';
|
||||||
|
|
||||||
|
if (!!row.shipped) {
|
||||||
|
delete_disabled = true;
|
||||||
|
title = '{% trans "Cannot be deleted as items have been shipped" %}';
|
||||||
|
} else if (!!row.allocated) {
|
||||||
|
delete_disabled = true;
|
||||||
|
title = '{% trans "Cannot be deleted as items have been allocated" %}';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent deletion of the line item if items have been allocated or shipped!
|
||||||
|
html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, title, {disabled: delete_disabled});
|
||||||
|
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|
||||||
|
@ -310,6 +310,7 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters for the PurchaseOrder table
|
// Filters for the PurchaseOrder table
|
||||||
if (tableKey == 'purchaseorder') {
|
if (tableKey == 'purchaseorder') {
|
||||||
|
|
||||||
@ -346,6 +347,15 @@ function getAvailableTableFilters(tableKey) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tableKey == 'salesorderlineitem') {
|
||||||
|
return {
|
||||||
|
completed: {
|
||||||
|
type: 'bool',
|
||||||
|
title: '{% trans "Completed" %}',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (tableKey == 'supplier-part') {
|
if (tableKey == 'supplier-part') {
|
||||||
return {
|
return {
|
||||||
active: {
|
active: {
|
||||||
|
Loading…
Reference in New Issue
Block a user