mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-02 03:30:54 +00:00
Add confirmation field for incomplete purchase orders (#3615)
* Add confirmation field for incomplete purchase orders * Add similar functionality for SalesOrder - Complete form is now context sensitive - Allow user to specify if order should be closed with incomplete lines * Update API version * JS linting * Updated unit tests
This commit is contained in:
@ -702,7 +702,7 @@ class SalesOrder(Order):
|
||||
"""Check if this order is "shipped" (all line items delivered)."""
|
||||
return self.lines.count() > 0 and all([line.is_completed() for line in self.lines.all()])
|
||||
|
||||
def can_complete(self, raise_error=False):
|
||||
def can_complete(self, raise_error=False, allow_incomplete_lines=False):
|
||||
"""Test if this SalesOrder can be completed.
|
||||
|
||||
Throws a ValidationError if cannot be completed.
|
||||
@ -720,7 +720,7 @@ class SalesOrder(Order):
|
||||
elif self.pending_shipment_count > 0:
|
||||
raise ValidationError(_("Order cannot be completed as there are incomplete shipments"))
|
||||
|
||||
elif self.pending_line_count > 0:
|
||||
elif not allow_incomplete_lines and self.pending_line_count > 0:
|
||||
raise ValidationError(_("Order cannot be completed as there are incomplete line items"))
|
||||
|
||||
except ValidationError as e:
|
||||
@ -732,9 +732,9 @@ class SalesOrder(Order):
|
||||
|
||||
return True
|
||||
|
||||
def complete_order(self, user):
|
||||
def complete_order(self, user, **kwargs):
|
||||
"""Mark this order as "complete."""
|
||||
if not self.can_complete():
|
||||
if not self.can_complete(**kwargs):
|
||||
return False
|
||||
|
||||
self.status = SalesOrderStatus.SHIPPED
|
||||
|
@ -19,7 +19,7 @@ import stock.models
|
||||
import stock.serializers
|
||||
from common.settings import currency_code_mappings
|
||||
from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
|
||||
from InvenTree.helpers import extract_serial_numbers, normalize
|
||||
from InvenTree.helpers import extract_serial_numbers, normalize, str2bool
|
||||
from InvenTree.serializers import (InvenTreeAttachmentSerializer,
|
||||
InvenTreeDecimalField,
|
||||
InvenTreeModelSerializer,
|
||||
@ -204,6 +204,23 @@ class PurchaseOrderCancelSerializer(serializers.Serializer):
|
||||
class PurchaseOrderCompleteSerializer(serializers.Serializer):
|
||||
"""Serializer for completing a purchase order."""
|
||||
|
||||
accept_incomplete = serializers.BooleanField(
|
||||
label=_('Accept Incomplete'),
|
||||
help_text=_('Allow order to be closed with incomplete line items'),
|
||||
required=False,
|
||||
default=False,
|
||||
)
|
||||
|
||||
def validate_accept_incomplete(self, value):
|
||||
"""Check if the 'accept_incomplete' field is required"""
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
if not value and not order.is_complete:
|
||||
raise ValidationError(_("Order has incomplete line items"))
|
||||
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
@ -1079,13 +1096,43 @@ class SalesOrderShipmentAllocationItemSerializer(serializers.Serializer):
|
||||
class SalesOrderCompleteSerializer(serializers.Serializer):
|
||||
"""DRF serializer for manually marking a sales order as complete."""
|
||||
|
||||
accept_incomplete = serializers.BooleanField(
|
||||
label=_('Accept Incomplete'),
|
||||
help_text=_('Allow order to be closed with incomplete line items'),
|
||||
required=False,
|
||||
default=False,
|
||||
)
|
||||
|
||||
def validate_accept_incomplete(self, value):
|
||||
"""Check if the 'accept_incomplete' field is required"""
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
if not value and not order.is_completed():
|
||||
raise ValidationError(_("Order has incomplete line items"))
|
||||
|
||||
return value
|
||||
|
||||
def get_context_data(self):
|
||||
"""Custom context data for this serializer"""
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
return {
|
||||
'is_complete': order.is_completed(),
|
||||
'pending_shipments': order.pending_shipment_count,
|
||||
}
|
||||
|
||||
def validate(self, data):
|
||||
"""Custom validation for the serializer"""
|
||||
data = super().validate(data)
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
order.can_complete(raise_error=True)
|
||||
order.can_complete(
|
||||
raise_error=True,
|
||||
allow_incomplete_lines=str2bool(data.get('accept_incomplete', False)),
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@ -1093,10 +1140,14 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
|
||||
"""Save the serializer to complete the SalesOrder"""
|
||||
request = self.context['request']
|
||||
order = self.context['order']
|
||||
data = self.validated_data
|
||||
|
||||
user = getattr(request, 'user', None)
|
||||
|
||||
order.complete_order(user)
|
||||
order.complete_order(
|
||||
user,
|
||||
allow_incomplete_lines=str2bool(data.get('accept_incomplete', False)),
|
||||
)
|
||||
|
||||
|
||||
class SalesOrderCancelSerializer(serializers.Serializer):
|
||||
|
@ -64,7 +64,7 @@ src="{% static 'img/blank_image.png' %}"
|
||||
|
||||
</div>
|
||||
{% if order.status == SalesOrderStatus.PENDING %}
|
||||
<button type='button' class='btn btn-success' id='complete-order' title='{% trans "Complete Sales Order" %}'{% if not order.is_completed %} disabled{% endif %}>
|
||||
<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 %}
|
||||
@ -253,12 +253,12 @@ $("#cancel-order").click(function() {
|
||||
});
|
||||
|
||||
$("#complete-order").click(function() {
|
||||
constructForm('{% url "api-so-complete" order.id %}', {
|
||||
method: 'POST',
|
||||
title: '{% trans "Complete Sales Order" %}',
|
||||
confirm: true,
|
||||
reload: true,
|
||||
});
|
||||
completeSalesOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
{% if report_enabled %}
|
||||
|
@ -322,7 +322,19 @@ class PurchaseOrderTest(OrderTest):
|
||||
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
self.post(url, {}, expected_code=201)
|
||||
# Should fail due to incomplete lines
|
||||
response = self.post(url, {}, expected_code=400)
|
||||
|
||||
self.assertIn('Order has incomplete line items', str(response.data['accept_incomplete']))
|
||||
|
||||
# Post again, accepting incomplete line items
|
||||
self.post(
|
||||
url,
|
||||
{
|
||||
'accept_incomplete': True,
|
||||
},
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
po.refresh_from_db()
|
||||
|
||||
|
Reference in New Issue
Block a user