mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 11:10:54 +00:00
Sales order status update (#4542)
* Add extra status code "IN PROGRESS" for sales order * Add method to 'issue' a SalesOrder * Updates to salesorder template * Add API endpoint and serializer for issuing a SalesOrder * javascript for issuing order * Cleanup buttons for SalesOrderLineItem table * Bug fixes
This commit is contained in:
@ -918,6 +918,8 @@ class SalesOrderExtraLineItemMetadata(RetrieveUpdateAPI):
|
||||
class SalesOrderContextMixin:
|
||||
"""Mixin to add sales order object as serializer context variable."""
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
|
||||
def get_serializer_context(self):
|
||||
"""Add the 'order' reference to the serializer context for any classes which inherit this mixin"""
|
||||
ctx = super().get_serializer_context()
|
||||
@ -934,15 +936,17 @@ class SalesOrderContextMixin:
|
||||
|
||||
class SalesOrderCancel(SalesOrderContextMixin, CreateAPI):
|
||||
"""API endpoint to cancel a SalesOrder"""
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
serializer_class = serializers.SalesOrderCancelSerializer
|
||||
|
||||
|
||||
class SalesOrderIssue(SalesOrderContextMixin, CreateAPI):
|
||||
"""API endpoint to issue a SalesOrder"""
|
||||
serializer_class = serializers.SalesOrderIssueSerializer
|
||||
|
||||
|
||||
class SalesOrderComplete(SalesOrderContextMixin, CreateAPI):
|
||||
"""API endpoint for manually marking a SalesOrder as "complete"."""
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
serializer_class = serializers.SalesOrderCompleteSerializer
|
||||
|
||||
|
||||
@ -1649,6 +1653,7 @@ order_api_urls = [
|
||||
re_path(r'^allocate/', SalesOrderAllocate.as_view(), name='api-so-allocate'),
|
||||
re_path(r'^allocate-serials/', SalesOrderAllocateSerials.as_view(), name='api-so-allocate-serials'),
|
||||
re_path(r'^cancel/', SalesOrderCancel.as_view(), name='api-so-cancel'),
|
||||
re_path(r'^issue/', SalesOrderIssue.as_view(), name='api-so-issue'),
|
||||
re_path(r'^complete/', SalesOrderComplete.as_view(), name='api-so-complete'),
|
||||
re_path(r'^metadata/', SalesOrderMetadata.as_view(), name='api-so-metadata'),
|
||||
|
||||
|
18
InvenTree/order/migrations/0087_alter_salesorder_status.py
Normal file
18
InvenTree/order/migrations/0087_alter_salesorder_status.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.18 on 2023-03-30 11:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0086_auto_20230323_1108'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='salesorder',
|
||||
name='status',
|
||||
field=models.PositiveIntegerField(choices=[(10, 'Pending'), (15, 'In Progress'), (20, 'Shipped'), (40, 'Cancelled'), (50, 'Lost'), (60, 'Returned')], default=10, help_text='Purchase order status', verbose_name='Status'),
|
||||
),
|
||||
]
|
@ -770,6 +770,11 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
"""Return True if this order is 'pending'"""
|
||||
return self.status == SalesOrderStatus.PENDING
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
"""Return True if this order is 'open' (either 'pending' or 'in_progress')"""
|
||||
return self.status in SalesOrderStatus.OPEN
|
||||
|
||||
@property
|
||||
def stock_allocations(self):
|
||||
"""Return a queryset containing all allocations for this order."""
|
||||
@ -827,6 +832,21 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
|
||||
return True
|
||||
|
||||
def place_order(self):
|
||||
"""Deprecated version of 'issue_order'"""
|
||||
self.issue_order()
|
||||
|
||||
@transaction.atomic
|
||||
def issue_order(self):
|
||||
"""Change this order from 'PENDING' to 'IN_PROGRESS'"""
|
||||
|
||||
if self.status == SalesOrderStatus.PENDING:
|
||||
self.status = SalesOrderStatus.IN_PROGRESS
|
||||
self.issue_date = datetime.now().date()
|
||||
self.save()
|
||||
|
||||
trigger_event('salesorder.issued', id=self.pk)
|
||||
|
||||
def complete_order(self, user, **kwargs):
|
||||
"""Mark this order as "complete."""
|
||||
if not self.can_complete(**kwargs):
|
||||
@ -1717,8 +1737,12 @@ class ReturnOrder(TotalPriceMixin, Order):
|
||||
|
||||
trigger_event('returnorder.completed', id=self.pk)
|
||||
|
||||
@transaction.atomic
|
||||
def place_order(self):
|
||||
"""Deprecated version of 'issue_order"""
|
||||
self.issue_order()
|
||||
|
||||
@transaction.atomic
|
||||
def issue_order(self):
|
||||
"""Issue this ReturnOrder (if currently pending)"""
|
||||
|
||||
if self.status == ReturnOrderStatus.PENDING:
|
||||
@ -1726,7 +1750,7 @@ class ReturnOrder(TotalPriceMixin, Order):
|
||||
self.issue_date = datetime.now().date()
|
||||
self.save()
|
||||
|
||||
trigger_event('returnorder.placed', id=self.pk)
|
||||
trigger_event('returnorder.issued', id=self.pk)
|
||||
|
||||
@transaction.atomic
|
||||
def receive_line_item(self, line, location, user, note=''):
|
||||
|
@ -731,6 +731,19 @@ class SalesOrderSerializer(TotalPriceMixin, AbstractOrderSerializer, InvenTreeMo
|
||||
customer_detail = CompanyBriefSerializer(source='customer', many=False, read_only=True)
|
||||
|
||||
|
||||
class SalesOrderIssueSerializer(serializers.Serializer):
|
||||
"""Serializer for issuing a SalesOrder"""
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options"""
|
||||
fields = []
|
||||
|
||||
def save(self):
|
||||
"""Save the serializer to 'issue' the order"""
|
||||
order = self.context['order']
|
||||
order.issue_order()
|
||||
|
||||
|
||||
class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
|
||||
"""Serializer for the SalesOrderAllocation model.
|
||||
|
||||
@ -1461,7 +1474,7 @@ class ReturnOrderIssueSerializer(serializers.Serializer):
|
||||
def save(self):
|
||||
"""Save the serializer to 'issue' the order"""
|
||||
order = self.context['order']
|
||||
order.place_order()
|
||||
order.issue_order()
|
||||
|
||||
|
||||
class ReturnOrderCancelSerializer(serializers.Serializer):
|
||||
|
@ -62,8 +62,8 @@ src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if order.status == ReturnOrderStatus.PENDING %}
|
||||
<button type='button' class='btn btn-primary' id='submit-order' title='{% trans "Submit Order" %}'>
|
||||
<span class='fas fa-paper-plane'></span> {% trans "Submit Order" %}
|
||||
<button type='button' class='btn btn-primary' id='issue-order' title='{% trans "Issue Order" %}'>
|
||||
<span class='fas fa-paper-plane'></span> {% trans "Issue Order" %}
|
||||
</button>
|
||||
{% elif order.status == ReturnOrderStatus.IN_PROGRESS %}
|
||||
<button type='button' class='btn btn-success' id='complete-order' title='{% trans "Mark order as complete" %}'>
|
||||
@ -186,7 +186,7 @@ src="{% static 'img/blank_image.png' %}"
|
||||
{% if roles.return_order.change %}
|
||||
|
||||
{% if order.status == ReturnOrderStatus.PENDING %}
|
||||
$('#submit-order').click(function() {
|
||||
$('#issue-order').click(function() {
|
||||
issueReturnOrder({{ order.pk }}, {
|
||||
reload: true,
|
||||
});
|
||||
|
@ -56,18 +56,27 @@ src="{% static 'img/blank_image.png' %}"
|
||||
</button>
|
||||
<ul class='dropdown-menu' role='menu'>
|
||||
<li><a class='dropdown-item' href='#' id='edit-order'><span class='fas fa-edit icon-green'></span> {% trans "Edit order" %}</a></li>
|
||||
{% if order.status == SalesOrderStatus.PENDING %}
|
||||
<li><a class='dropdown-item' href='#' id='complete-order-shipments'><span class='fas fa-truck'></span> {% trans "Complete Shipments" %}</a></li>
|
||||
{% if order.is_open %}
|
||||
<li><a class='dropdown-item' href='#' id='cancel-order'><span class='fas fa-times-circle icon-red'></span> {% trans "Cancel order" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
{% if order.status == SalesOrderStatus.PENDING %}
|
||||
<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 %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if order.is_pending %}
|
||||
<button type='button' class='btn btn-primary' id='issue-order' title='{% trans "Issue Order" %}'>
|
||||
<span class='fas fa-paper-plane'></span> {% trans "Issue Order" %}
|
||||
</button>
|
||||
{% elif order.status == SalesOrderStatus.IN_PROGRESS %}
|
||||
{% if not order.is_completed %}
|
||||
<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>
|
||||
{% 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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock actions %}
|
||||
|
||||
@ -232,6 +241,15 @@ $("#complete-order-shipments").click(function() {
|
||||
);
|
||||
});
|
||||
|
||||
$('#issue-order').click(function() {
|
||||
issueSalesOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
reload: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
|
||||
cancelSalesOrder(
|
||||
|
@ -19,7 +19,7 @@
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if roles.sales_order.add %}
|
||||
{% if order.is_pending or allow_extra_editing %}
|
||||
{% if order.is_open or allow_extra_editing %}
|
||||
<button type='button' class='btn btn-success' id='new-so-line'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "Add Line Item" %}
|
||||
</button>
|
||||
@ -44,7 +44,7 @@
|
||||
{% include "spacer.html" %}
|
||||
<div class='btn-group' role='group'>
|
||||
{% if roles.sales_order.change %}
|
||||
{% if order.is_pending or allow_extra_editing %}
|
||||
{% if order.is_open or allow_extra_editing %}
|
||||
<button type='button' class='btn btn-success' id='new-so-extra-line'>
|
||||
<span class='fas fa-plus-circle'></span> {% trans "Add Extra Line" %}
|
||||
</button>
|
||||
@ -253,11 +253,18 @@
|
||||
order: {{ order.pk }},
|
||||
reference: '{{ order.reference }}',
|
||||
status: {{ order.status }},
|
||||
open: {% js_bool order.is_open %},
|
||||
{% if roles.sales_order.change %}
|
||||
{% settings_value "SALESORDER_EDIT_COMPLETED_ORDERS" as allow_edit %}
|
||||
{% if allow_edit or order.is_open %}
|
||||
allow_edit: true,
|
||||
{% endif %}
|
||||
{% if order.is_pending %}
|
||||
pending: true,
|
||||
{% if order.status == SalesOrderStatus.IN_PROGRESS %}
|
||||
allow_ship: true,
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if roles.sales_order.delete %}
|
||||
allow_delete: true,
|
||||
{% endif %}
|
||||
}
|
||||
);
|
||||
@ -281,7 +288,7 @@
|
||||
name: 'salesorderextraline',
|
||||
filtertarget: '#filter-list-sales-order-extra-lines',
|
||||
{% settings_value "SALESORDER_EDIT_COMPLETED_ORDERS" as allow_edit %}
|
||||
{% if order.is_pending or allow_edit %}
|
||||
{% if order.is_open or allow_edit %}
|
||||
allow_edit: {% js_bool roles.sales_order.change %},
|
||||
allow_delete: {% js_bool roles.sales_order.delete %},
|
||||
{% else %}
|
||||
|
Reference in New Issue
Block a user