mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
* Add support for a Completed status (Fixes: #6434) * Remove unnecessary validator * Update sales order tests for the new model functions
This commit is contained in:
parent
b7b320cf61
commit
e8f8f3b3ec
@ -41,6 +41,7 @@ class SalesOrderStatus(StatusCode):
|
||||
'primary',
|
||||
) # Order has been issued, and is in progress
|
||||
SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer
|
||||
COMPLETE = 30, _('Complete'), 'success' # Order is complete
|
||||
CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled
|
||||
LOST = 50, _('Lost'), 'warning' # Order was lost
|
||||
RETURNED = 60, _('Returned'), 'warning' # Order was returned
|
||||
@ -53,7 +54,7 @@ class SalesOrderStatusGroups:
|
||||
OPEN = [SalesOrderStatus.PENDING.value, SalesOrderStatus.IN_PROGRESS.value]
|
||||
|
||||
# Completed orders
|
||||
COMPLETE = [SalesOrderStatus.SHIPPED.value]
|
||||
COMPLETE = [SalesOrderStatus.SHIPPED.value, SalesOrderStatus.COMPLETE.value]
|
||||
|
||||
|
||||
class StockStatus(StatusCode):
|
||||
|
@ -0,0 +1,20 @@
|
||||
# Generated by Django 4.2.11 on 2024-04-03 00:40
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import InvenTree.status_codes
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0098_auto_20231024_1844'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='salesorder',
|
||||
name='status',
|
||||
field=models.PositiveIntegerField(choices=InvenTree.status_codes.SalesOrderStatus.items(), default=10, help_text='Purchase order status', verbose_name='Status'),
|
||||
),
|
||||
]
|
@ -1000,7 +1000,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
)
|
||||
|
||||
# Only an open order can be marked as shipped
|
||||
elif not self.is_open:
|
||||
elif not self.is_open and not self.is_completed:
|
||||
raise ValidationError(_('Only an open order can be marked as complete'))
|
||||
|
||||
elif self.pending_shipment_count > 0:
|
||||
@ -1042,9 +1042,12 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
if not self.can_complete(**kwargs):
|
||||
return False
|
||||
|
||||
self.status = SalesOrderStatus.SHIPPED.value
|
||||
self.shipped_by = user
|
||||
self.shipment_date = InvenTree.helpers.current_date()
|
||||
if self.status == SalesOrderStatus.SHIPPED:
|
||||
self.status = SalesOrderStatus.COMPLETE.value
|
||||
else:
|
||||
self.status = SalesOrderStatus.SHIPPED.value
|
||||
self.shipped_by = user
|
||||
self.shipment_date = InvenTree.helpers.current_date()
|
||||
|
||||
self.save()
|
||||
|
||||
@ -1098,7 +1101,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def complete_order(self, user, **kwargs):
|
||||
def ship_order(self, user, **kwargs):
|
||||
"""Attempt to transition to SHIPPED status."""
|
||||
return self.handle_transition(
|
||||
self.status,
|
||||
@ -1109,6 +1112,18 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def complete_order(self, user, **kwargs):
|
||||
"""Attempt to transition to COMPLETED status."""
|
||||
return self.handle_transition(
|
||||
self.status,
|
||||
SalesOrderStatus.COMPLETED.value,
|
||||
self,
|
||||
self._action_complete,
|
||||
user=user,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def cancel_order(self):
|
||||
"""Attempt to transition to CANCELLED status."""
|
||||
|
@ -1288,7 +1288,7 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
|
||||
|
||||
user = getattr(request, 'user', None)
|
||||
|
||||
order.complete_order(
|
||||
order.ship_order(
|
||||
user, allow_incomplete_lines=str2bool(data.get('accept_incomplete', False))
|
||||
)
|
||||
|
||||
|
@ -88,7 +88,11 @@ src="{% static 'img/blank_image.png' %}"
|
||||
<button type='button' class='btn btn-success' id='complete-order-shipments' title='{% trans "Ship Items" %}'>
|
||||
<span class='fas fa-truck'></span> {% trans "Ship Items" %}
|
||||
</button>
|
||||
<button type='button' class='btn btn-success' id='ship-order' title='{% trans "Mark As Shipped" %}'>
|
||||
<span class='fas fa-check-circle'></span> {% trans "Mark As Shipped" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif order.status == SalesOrderStatus.SHIPPED %}
|
||||
<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>
|
||||
@ -286,6 +290,15 @@ $("#cancel-order").click(function() {
|
||||
);
|
||||
});
|
||||
|
||||
$("#ship-order").click(function() {
|
||||
shipSalesOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#complete-order").click(function() {
|
||||
completeSalesOrder(
|
||||
{{ order.pk }},
|
||||
|
@ -211,7 +211,7 @@ class SalesOrderTest(TestCase):
|
||||
self.order.can_complete(raise_error=True)
|
||||
|
||||
# Now try to ship it - should fail
|
||||
result = self.order.complete_order(None)
|
||||
result = self.order.ship_order(None)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_complete_order(self):
|
||||
@ -225,8 +225,8 @@ class SalesOrderTest(TestCase):
|
||||
|
||||
self.assertEqual(SalesOrderAllocation.objects.count(), 2)
|
||||
|
||||
# Attempt to complete the order (but shipments are not completed!)
|
||||
result = self.order.complete_order(None)
|
||||
# Attempt to ship the order (but shipments are not completed!)
|
||||
result = self.order.ship_order(None)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
@ -238,7 +238,7 @@ class SalesOrderTest(TestCase):
|
||||
self.assertTrue(self.shipment.is_complete())
|
||||
|
||||
# Now, should be OK to ship
|
||||
result = self.order.complete_order(None)
|
||||
result = self.order.ship_order(None)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
|
@ -473,7 +473,59 @@ function completePendingShipmentsHelper(shipments, shipment_idx, options={}) {
|
||||
|
||||
|
||||
/*
|
||||
* Launches a modal form to mark a SalesOrder as "complete"
|
||||
* Launches a modal form to mark a SalesOrder as "shipped"
|
||||
*/
|
||||
function shipSalesOrder(order_id, options={}) {
|
||||
|
||||
constructForm(
|
||||
`/api/order/so/${order_id}/complete/`,
|
||||
{
|
||||
method: 'POST',
|
||||
title: '{% trans "Ship Sales Order" %}',
|
||||
confirm: true,
|
||||
fieldsFunction: function(opts) {
|
||||
var fields = {
|
||||
accept_incomplete: {},
|
||||
};
|
||||
|
||||
if (opts.context.is_complete) {
|
||||
delete fields['accept_incomplete'];
|
||||
}
|
||||
|
||||
return fields;
|
||||
},
|
||||
preFormContent: function(opts) {
|
||||
var html = `
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "Ship this order?" %}
|
||||
</div>`;
|
||||
|
||||
if (opts.context.pending_shipments) {
|
||||
html += `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "Order cannot be shipped as there are incomplete shipments" %}<br>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (!opts.context.is_complete) {
|
||||
html += `
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This order has line items which have not been completed." %}<br>
|
||||
{% trans "Shipping this order means that the order and line items will no longer be editable." %}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
onSuccess: function(response) {
|
||||
handleFormSuccess(response, options);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Launches a modal form to mark a SalesOrder as "completed"
|
||||
*/
|
||||
function completeSalesOrder(order_id, options={}) {
|
||||
|
||||
@ -500,21 +552,6 @@ function completeSalesOrder(order_id, options={}) {
|
||||
{% trans "Mark this order as complete?" %}
|
||||
</div>`;
|
||||
|
||||
if (opts.context.pending_shipments) {
|
||||
html += `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "Order cannot be completed as there are incomplete shipments" %}<br>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (!opts.context.is_complete) {
|
||||
html += `
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This order has line items which have not been completed." %}<br>
|
||||
{% trans "Completing this order means that the order and line items will no longer be editable." %}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
},
|
||||
onSuccess: function(response) {
|
||||
|
Loading…
Reference in New Issue
Block a user