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',
|
'primary',
|
||||||
) # Order has been issued, and is in progress
|
) # Order has been issued, and is in progress
|
||||||
SHIPPED = 20, _('Shipped'), 'success' # Order has been shipped to customer
|
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
|
CANCELLED = 40, _('Cancelled'), 'danger' # Order has been cancelled
|
||||||
LOST = 50, _('Lost'), 'warning' # Order was lost
|
LOST = 50, _('Lost'), 'warning' # Order was lost
|
||||||
RETURNED = 60, _('Returned'), 'warning' # Order was returned
|
RETURNED = 60, _('Returned'), 'warning' # Order was returned
|
||||||
@ -53,7 +54,7 @@ class SalesOrderStatusGroups:
|
|||||||
OPEN = [SalesOrderStatus.PENDING.value, SalesOrderStatus.IN_PROGRESS.value]
|
OPEN = [SalesOrderStatus.PENDING.value, SalesOrderStatus.IN_PROGRESS.value]
|
||||||
|
|
||||||
# Completed orders
|
# Completed orders
|
||||||
COMPLETE = [SalesOrderStatus.SHIPPED.value]
|
COMPLETE = [SalesOrderStatus.SHIPPED.value, SalesOrderStatus.COMPLETE.value]
|
||||||
|
|
||||||
|
|
||||||
class StockStatus(StatusCode):
|
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
|
# 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'))
|
raise ValidationError(_('Only an open order can be marked as complete'))
|
||||||
|
|
||||||
elif self.pending_shipment_count > 0:
|
elif self.pending_shipment_count > 0:
|
||||||
@ -1042,9 +1042,12 @@ class SalesOrder(TotalPriceMixin, Order):
|
|||||||
if not self.can_complete(**kwargs):
|
if not self.can_complete(**kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.status = SalesOrderStatus.SHIPPED.value
|
if self.status == SalesOrderStatus.SHIPPED:
|
||||||
self.shipped_by = user
|
self.status = SalesOrderStatus.COMPLETE.value
|
||||||
self.shipment_date = InvenTree.helpers.current_date()
|
else:
|
||||||
|
self.status = SalesOrderStatus.SHIPPED.value
|
||||||
|
self.shipped_by = user
|
||||||
|
self.shipment_date = InvenTree.helpers.current_date()
|
||||||
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
@ -1098,7 +1101,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def complete_order(self, user, **kwargs):
|
def ship_order(self, user, **kwargs):
|
||||||
"""Attempt to transition to SHIPPED status."""
|
"""Attempt to transition to SHIPPED status."""
|
||||||
return self.handle_transition(
|
return self.handle_transition(
|
||||||
self.status,
|
self.status,
|
||||||
@ -1109,6 +1112,18 @@ class SalesOrder(TotalPriceMixin, Order):
|
|||||||
**kwargs,
|
**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
|
@transaction.atomic
|
||||||
def cancel_order(self):
|
def cancel_order(self):
|
||||||
"""Attempt to transition to CANCELLED status."""
|
"""Attempt to transition to CANCELLED status."""
|
||||||
|
@ -1288,7 +1288,7 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
user = getattr(request, 'user', None)
|
user = getattr(request, 'user', None)
|
||||||
|
|
||||||
order.complete_order(
|
order.ship_order(
|
||||||
user, allow_incomplete_lines=str2bool(data.get('accept_incomplete', False))
|
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" %}'>
|
<button type='button' class='btn btn-success' id='complete-order-shipments' title='{% trans "Ship Items" %}'>
|
||||||
<span class='fas fa-truck'></span> {% trans "Ship Items" %}
|
<span class='fas fa-truck'></span> {% trans "Ship Items" %}
|
||||||
</button>
|
</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 %}
|
{% endif %}
|
||||||
|
{% elif order.status == SalesOrderStatus.SHIPPED %}
|
||||||
<button type='button' class='btn btn-success' id='complete-order' title='{% trans "Complete Sales 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" %}
|
<span class='fas fa-check-circle'></span> {% trans "Complete Order" %}
|
||||||
</button>
|
</button>
|
||||||
@ -286,6 +290,15 @@ $("#cancel-order").click(function() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#ship-order").click(function() {
|
||||||
|
shipSalesOrder(
|
||||||
|
{{ order.pk }},
|
||||||
|
{
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
$("#complete-order").click(function() {
|
$("#complete-order").click(function() {
|
||||||
completeSalesOrder(
|
completeSalesOrder(
|
||||||
{{ order.pk }},
|
{{ order.pk }},
|
||||||
|
@ -211,7 +211,7 @@ class SalesOrderTest(TestCase):
|
|||||||
self.order.can_complete(raise_error=True)
|
self.order.can_complete(raise_error=True)
|
||||||
|
|
||||||
# Now try to ship it - should fail
|
# Now try to ship it - should fail
|
||||||
result = self.order.complete_order(None)
|
result = self.order.ship_order(None)
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
|
|
||||||
def test_complete_order(self):
|
def test_complete_order(self):
|
||||||
@ -225,8 +225,8 @@ class SalesOrderTest(TestCase):
|
|||||||
|
|
||||||
self.assertEqual(SalesOrderAllocation.objects.count(), 2)
|
self.assertEqual(SalesOrderAllocation.objects.count(), 2)
|
||||||
|
|
||||||
# Attempt to complete the order (but shipments are not completed!)
|
# Attempt to ship the order (but shipments are not completed!)
|
||||||
result = self.order.complete_order(None)
|
result = self.order.ship_order(None)
|
||||||
|
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
|
|
||||||
@ -238,7 +238,7 @@ class SalesOrderTest(TestCase):
|
|||||||
self.assertTrue(self.shipment.is_complete())
|
self.assertTrue(self.shipment.is_complete())
|
||||||
|
|
||||||
# Now, should be OK to ship
|
# Now, should be OK to ship
|
||||||
result = self.order.complete_order(None)
|
result = self.order.ship_order(None)
|
||||||
|
|
||||||
self.assertTrue(result)
|
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={}) {
|
function completeSalesOrder(order_id, options={}) {
|
||||||
|
|
||||||
@ -500,21 +552,6 @@ function completeSalesOrder(order_id, options={}) {
|
|||||||
{% trans "Mark this order as complete?" %}
|
{% trans "Mark this order as complete?" %}
|
||||||
</div>`;
|
</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;
|
return html;
|
||||||
},
|
},
|
||||||
onSuccess: function(response) {
|
onSuccess: function(response) {
|
||||||
|
Loading…
Reference in New Issue
Block a user