mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-17 12:35:46 +00:00
Merge branch 'inventree:master' into so_fix_clean
This commit is contained in:
@ -286,7 +286,58 @@ class PurchaseOrderDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
return queryset
|
||||
|
||||
|
||||
class PurchaseOrderReceive(generics.CreateAPIView):
|
||||
class PurchaseOrderContextMixin:
|
||||
""" Mixin to add purchase order object as serializer context variable """
|
||||
|
||||
def get_serializer_context(self):
|
||||
""" Add the PurchaseOrder object to the serializer context """
|
||||
|
||||
context = super().get_serializer_context()
|
||||
|
||||
# Pass the purchase order through to the serializer for validation
|
||||
try:
|
||||
context['order'] = models.PurchaseOrder.objects.get(pk=self.kwargs.get('pk', None))
|
||||
except:
|
||||
pass
|
||||
|
||||
context['request'] = self.request
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class PurchaseOrderCancel(PurchaseOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to 'cancel' a purchase order.
|
||||
|
||||
The purchase order must be in a state which can be cancelled
|
||||
"""
|
||||
|
||||
queryset = models.PurchaseOrder.objects.all()
|
||||
|
||||
serializer_class = serializers.PurchaseOrderCancelSerializer
|
||||
|
||||
|
||||
class PurchaseOrderComplete(PurchaseOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to 'complete' a purchase order
|
||||
"""
|
||||
|
||||
queryset = models.PurchaseOrder.objects.all()
|
||||
|
||||
serializer_class = serializers.PurchaseOrderCompleteSerializer
|
||||
|
||||
|
||||
class PurchaseOrderIssue(PurchaseOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to 'complete' a purchase order
|
||||
"""
|
||||
|
||||
queryset = models.PurchaseOrder.objects.all()
|
||||
|
||||
serializer_class = serializers.PurchaseOrderIssueSerializer
|
||||
|
||||
|
||||
class PurchaseOrderReceive(PurchaseOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to receive stock items against a purchase order.
|
||||
|
||||
@ -303,20 +354,6 @@ class PurchaseOrderReceive(generics.CreateAPIView):
|
||||
|
||||
serializer_class = serializers.PurchaseOrderReceiveSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
||||
context = super().get_serializer_context()
|
||||
|
||||
# Pass the purchase order through to the serializer for validation
|
||||
try:
|
||||
context['order'] = models.PurchaseOrder.objects.get(pk=self.kwargs.get('pk', None))
|
||||
except:
|
||||
pass
|
||||
|
||||
context['request'] = self.request
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class PurchaseOrderLineItemFilter(rest_filters.FilterSet):
|
||||
"""
|
||||
@ -834,13 +871,8 @@ class SalesOrderLineItemDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
serializer_class = serializers.SalesOrderLineItemSerializer
|
||||
|
||||
|
||||
class SalesOrderComplete(generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint for manually marking a SalesOrder as "complete".
|
||||
"""
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
serializer_class = serializers.SalesOrderCompleteSerializer
|
||||
class SalesOrderContextMixin:
|
||||
""" Mixin to add sales order object as serializer context variable """
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
||||
@ -856,7 +888,22 @@ class SalesOrderComplete(generics.CreateAPIView):
|
||||
return ctx
|
||||
|
||||
|
||||
class SalesOrderAllocateSerials(generics.CreateAPIView):
|
||||
class SalesOrderCancel(SalesOrderContextMixin, generics.CreateAPIView):
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
serializer_class = serializers.SalesOrderCancelSerializer
|
||||
|
||||
|
||||
class SalesOrderComplete(SalesOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint for manually marking a SalesOrder as "complete".
|
||||
"""
|
||||
|
||||
queryset = models.SalesOrder.objects.all()
|
||||
serializer_class = serializers.SalesOrderCompleteSerializer
|
||||
|
||||
|
||||
class SalesOrderAllocateSerials(SalesOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to allocation stock items against a SalesOrder,
|
||||
by specifying serial numbers.
|
||||
@ -865,22 +912,8 @@ class SalesOrderAllocateSerials(generics.CreateAPIView):
|
||||
queryset = models.SalesOrder.objects.none()
|
||||
serializer_class = serializers.SalesOrderSerialAllocationSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
||||
ctx = super().get_serializer_context()
|
||||
|
||||
# Pass through the SalesOrder object to the serializer
|
||||
try:
|
||||
ctx['order'] = models.SalesOrder.objects.get(pk=self.kwargs.get('pk', None))
|
||||
except:
|
||||
pass
|
||||
|
||||
ctx['request'] = self.request
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class SalesOrderAllocate(generics.CreateAPIView):
|
||||
class SalesOrderAllocate(SalesOrderContextMixin, generics.CreateAPIView):
|
||||
"""
|
||||
API endpoint to allocate stock items against a SalesOrder
|
||||
|
||||
@ -891,20 +924,6 @@ class SalesOrderAllocate(generics.CreateAPIView):
|
||||
queryset = models.SalesOrder.objects.none()
|
||||
serializer_class = serializers.SalesOrderShipmentAllocationSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
|
||||
ctx = super().get_serializer_context()
|
||||
|
||||
# Pass through the SalesOrder object to the serializer
|
||||
try:
|
||||
ctx['order'] = models.SalesOrder.objects.get(pk=self.kwargs.get('pk', None))
|
||||
except:
|
||||
pass
|
||||
|
||||
ctx['request'] = self.request
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class SalesOrderAllocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
@ -1106,7 +1125,10 @@ order_api_urls = [
|
||||
|
||||
# Individual purchase order detail URLs
|
||||
re_path(r'^(?P<pk>\d+)/', include([
|
||||
re_path(r'^issue/', PurchaseOrderIssue.as_view(), name='api-po-issue'),
|
||||
re_path(r'^receive/', PurchaseOrderReceive.as_view(), name='api-po-receive'),
|
||||
re_path(r'^cancel/', PurchaseOrderCancel.as_view(), name='api-po-cancel'),
|
||||
re_path(r'^complete/', PurchaseOrderComplete.as_view(), name='api-po-complete'),
|
||||
re_path(r'.*$', PurchaseOrderDetail.as_view(), name='api-po-detail'),
|
||||
])),
|
||||
|
||||
@ -1143,6 +1165,7 @@ order_api_urls = [
|
||||
|
||||
# Sales order detail view
|
||||
re_path(r'^(?P<pk>\d+)/', include([
|
||||
re_path(r'^cancel/', SalesOrderCancel.as_view(), name='api-so-cancel'),
|
||||
re_path(r'^complete/', SalesOrderComplete.as_view(), name='api-so-complete'),
|
||||
re_path(r'^allocate/', SalesOrderAllocate.as_view(), name='api-so-allocate'),
|
||||
re_path(r'^allocate-serials/', SalesOrderAllocateSerials.as_view(), name='api-so-allocate-serials'),
|
||||
|
@ -8,60 +8,12 @@ from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from InvenTree.forms import HelperForm
|
||||
from InvenTree.fields import InvenTreeMoneyField
|
||||
|
||||
from InvenTree.helpers import clean_decimal
|
||||
|
||||
from common.forms import MatchItemForm
|
||||
|
||||
from .models import PurchaseOrder
|
||||
from .models import SalesOrder
|
||||
|
||||
|
||||
class IssuePurchaseOrderForm(HelperForm):
|
||||
|
||||
confirm = forms.BooleanField(required=True, initial=False, label=_('Confirm'), help_text=_('Place order'))
|
||||
|
||||
class Meta:
|
||||
model = PurchaseOrder
|
||||
fields = [
|
||||
'confirm',
|
||||
]
|
||||
|
||||
|
||||
class CompletePurchaseOrderForm(HelperForm):
|
||||
|
||||
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_("Mark order as complete"))
|
||||
|
||||
class Meta:
|
||||
model = PurchaseOrder
|
||||
fields = [
|
||||
'confirm',
|
||||
]
|
||||
|
||||
|
||||
class CancelPurchaseOrderForm(HelperForm):
|
||||
|
||||
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Cancel order'))
|
||||
|
||||
class Meta:
|
||||
model = PurchaseOrder
|
||||
fields = [
|
||||
'confirm',
|
||||
]
|
||||
|
||||
|
||||
class CancelSalesOrderForm(HelperForm):
|
||||
|
||||
confirm = forms.BooleanField(required=True, label=_('Confirm'), help_text=_('Cancel order'))
|
||||
|
||||
class Meta:
|
||||
model = SalesOrder
|
||||
fields = [
|
||||
'confirm',
|
||||
]
|
||||
|
||||
|
||||
class OrderMatchItemForm(MatchItemForm):
|
||||
""" Override MatchItemForm fields """
|
||||
|
@ -381,6 +381,7 @@ class PurchaseOrder(Order):
|
||||
PurchaseOrderStatus.PENDING
|
||||
]
|
||||
|
||||
@transaction.atomic
|
||||
def cancel_order(self):
|
||||
""" Marks the PurchaseOrder as CANCELLED. """
|
||||
|
||||
|
@ -179,6 +179,72 @@ class PurchaseOrderSerializer(AbstractOrderSerializer, ReferenceIndexingSerializ
|
||||
]
|
||||
|
||||
|
||||
class PurchaseOrderCancelSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for cancelling a PurchaseOrder
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
fields = [],
|
||||
|
||||
def get_context_data(self):
|
||||
"""
|
||||
Return custom context information about the order
|
||||
"""
|
||||
|
||||
self.order = self.context['order']
|
||||
|
||||
return {
|
||||
'can_cancel': self.order.can_cancel(),
|
||||
}
|
||||
|
||||
def save(self):
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
if not order.can_cancel():
|
||||
raise ValidationError(_("Order cannot be cancelled"))
|
||||
|
||||
order.cancel_order()
|
||||
|
||||
|
||||
class PurchaseOrderCompleteSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for completing a purchase order
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
fields = []
|
||||
|
||||
def get_context_data(self):
|
||||
"""
|
||||
Custom context information for this serializer
|
||||
"""
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
return {
|
||||
'is_complete': order.is_complete,
|
||||
}
|
||||
|
||||
def save(self):
|
||||
|
||||
order = self.context['order']
|
||||
order.complete_order()
|
||||
|
||||
|
||||
class PurchaseOrderIssueSerializer(serializers.Serializer):
|
||||
""" Serializer for issuing (sending) a purchase order """
|
||||
|
||||
class Meta:
|
||||
fields = []
|
||||
|
||||
def save(self):
|
||||
|
||||
order = self.context['order']
|
||||
order.place_order()
|
||||
|
||||
|
||||
class PurchaseOrderLineItemSerializer(InvenTreeModelSerializer):
|
||||
|
||||
@staticmethod
|
||||
@ -974,6 +1040,25 @@ class SalesOrderCompleteSerializer(serializers.Serializer):
|
||||
order.complete_order(user)
|
||||
|
||||
|
||||
class SalesOrderCancelSerializer(serializers.Serializer):
|
||||
""" Serializer for marking a SalesOrder as cancelled
|
||||
"""
|
||||
|
||||
def get_context_data(self):
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
return {
|
||||
'can_cancel': order.can_cancel(),
|
||||
}
|
||||
|
||||
def save(self):
|
||||
|
||||
order = self.context['order']
|
||||
|
||||
order.cancel_order()
|
||||
|
||||
|
||||
class SalesOrderSerialAllocationSerializer(serializers.Serializer):
|
||||
"""
|
||||
DRF serializer for allocation of serial numbers against a sales order / shipment
|
||||
|
@ -1,7 +0,0 @@
|
||||
{% extends "modal_delete_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
{% trans "Are you sure you want to delete this attachment?" %}
|
||||
<br>
|
||||
{% endblock %}
|
@ -192,10 +192,14 @@ src="{% static 'img/blank_image.png' %}"
|
||||
|
||||
{% if order.status == PurchaseOrderStatus.PENDING %}
|
||||
$("#place-order").click(function() {
|
||||
launchModalForm("{% url 'po-issue' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
});
|
||||
|
||||
issuePurchaseOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
@ -258,15 +262,27 @@ $("#receive-order").click(function() {
|
||||
});
|
||||
|
||||
$("#complete-order").click(function() {
|
||||
launchModalForm("{% url 'po-complete' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
|
||||
completePurchaseOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
onSuccess: function() {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'po-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
|
||||
cancelPurchaseOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
onSuccess: function() {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
$("#export-order").click(function() {
|
||||
|
@ -1,11 +0,0 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
<div class='alert alert-danger alert-block'>
|
||||
{% trans "Cancelling this order means that the order and line items will no longer be editable." %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,15 +0,0 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
{% trans 'Mark this order as complete?' %}
|
||||
{% if not order.is_complete %}
|
||||
<div class='alert alert-warning alert-block' style='margin-top:12px'>
|
||||
{% trans 'This order has line items which have not been marked as received.' %}</br>
|
||||
{% trans 'Completing this order means that the order and line items will no longer be editable.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -1,11 +0,0 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
<div class='alert alert-warning alert-block'>
|
||||
{% trans 'After placing this purchase order, line items will no longer be editable.' %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -224,9 +224,13 @@ $("#edit-order").click(function() {
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'so-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
|
||||
cancelSalesOrder(
|
||||
{{ order.pk }},
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#complete-order").click(function() {
|
||||
|
@ -1,12 +0,0 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
<div class='alert alert-block alert-warning'>
|
||||
<h4>{% trans "Warning" %}</h4>
|
||||
{% trans "Cancelling this order means that the order will no longer be editable." %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -9,7 +9,7 @@ from rest_framework import status
|
||||
from django.urls import reverse
|
||||
|
||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||
from InvenTree.status_codes import PurchaseOrderStatus
|
||||
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
|
||||
|
||||
from part.models import Part
|
||||
from stock.models import StockItem
|
||||
@ -239,6 +239,73 @@ class PurchaseOrderTest(OrderTest):
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
def test_po_cancel(self):
|
||||
"""
|
||||
Test the PurchaseOrderCancel API endpoint
|
||||
"""
|
||||
|
||||
po = models.PurchaseOrder.objects.get(pk=1)
|
||||
|
||||
self.assertEqual(po.status, PurchaseOrderStatus.PENDING)
|
||||
|
||||
url = reverse('api-po-cancel', kwargs={'pk': po.pk})
|
||||
|
||||
# Try to cancel the PO, but without reqiured permissions
|
||||
self.post(url, {}, expected_code=403)
|
||||
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
self.post(
|
||||
url,
|
||||
{},
|
||||
expected_code=201,
|
||||
)
|
||||
|
||||
po.refresh_from_db()
|
||||
|
||||
self.assertEqual(po.status, PurchaseOrderStatus.CANCELLED)
|
||||
|
||||
# Try to cancel again (should fail)
|
||||
self.post(url, {}, expected_code=400)
|
||||
|
||||
def test_po_complete(self):
|
||||
""" Test the PurchaseOrderComplete API endpoint """
|
||||
|
||||
po = models.PurchaseOrder.objects.get(pk=3)
|
||||
|
||||
url = reverse('api-po-complete', kwargs={'pk': po.pk})
|
||||
|
||||
self.assertEqual(po.status, PurchaseOrderStatus.PLACED)
|
||||
|
||||
# Try to complete the PO, without required permissions
|
||||
self.post(url, {}, expected_code=403)
|
||||
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
self.post(url, {}, expected_code=201)
|
||||
|
||||
po.refresh_from_db()
|
||||
|
||||
self.assertEqual(po.status, PurchaseOrderStatus.COMPLETE)
|
||||
|
||||
def test_po_issue(self):
|
||||
""" Test the PurchaseOrderIssue API endpoint """
|
||||
|
||||
po = models.PurchaseOrder.objects.get(pk=2)
|
||||
|
||||
url = reverse('api-po-issue', kwargs={'pk': po.pk})
|
||||
|
||||
# Try to issue the PO, without required permissions
|
||||
self.post(url, {}, expected_code=403)
|
||||
|
||||
self.assignRole('purchase_order.add')
|
||||
|
||||
self.post(url, {}, expected_code=201)
|
||||
|
||||
po.refresh_from_db()
|
||||
|
||||
self.assertEqual(po.status, PurchaseOrderStatus.PLACED)
|
||||
|
||||
|
||||
class PurchaseOrderReceiveTest(OrderTest):
|
||||
"""
|
||||
@ -788,6 +855,26 @@ class SalesOrderTest(OrderTest):
|
||||
expected_code=201
|
||||
)
|
||||
|
||||
def test_so_cancel(self):
|
||||
""" Test API endpoint for cancelling a SalesOrder """
|
||||
|
||||
so = models.SalesOrder.objects.get(pk=1)
|
||||
|
||||
self.assertEqual(so.status, SalesOrderStatus.PENDING)
|
||||
|
||||
url = reverse('api-so-cancel', kwargs={'pk': so.pk})
|
||||
|
||||
# Try to cancel, without permission
|
||||
self.post(url, {}, expected_code=403)
|
||||
|
||||
self.assignRole('sales_order.add')
|
||||
|
||||
self.post(url, {}, expected_code=201)
|
||||
|
||||
so.refresh_from_db()
|
||||
|
||||
self.assertEqual(so.status, SalesOrderStatus.CANCELLED)
|
||||
|
||||
|
||||
class SalesOrderAllocateTest(OrderTest):
|
||||
"""
|
||||
|
@ -8,12 +8,6 @@ from django.urls import reverse
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from InvenTree.status_codes import PurchaseOrderStatus
|
||||
|
||||
from .models import PurchaseOrder
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class OrderViewTestCase(TestCase):
|
||||
|
||||
@ -76,30 +70,3 @@ class PurchaseOrderTests(OrderViewTestCase):
|
||||
|
||||
# Response should be streaming-content (file download)
|
||||
self.assertIn('streaming_content', dir(response))
|
||||
|
||||
def test_po_issue(self):
|
||||
""" Test PurchaseOrderIssue view """
|
||||
|
||||
url = reverse('po-issue', args=(1,))
|
||||
|
||||
order = PurchaseOrder.objects.get(pk=1)
|
||||
self.assertEqual(order.status, PurchaseOrderStatus.PENDING)
|
||||
|
||||
# Test without confirmation
|
||||
response = self.client.post(url, {'confirm': 0}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = json.loads(response.content)
|
||||
|
||||
self.assertFalse(data['form_valid'])
|
||||
|
||||
# Test WITH confirmation
|
||||
response = self.client.post(url, {'confirm': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = json.loads(response.content)
|
||||
self.assertTrue(data['form_valid'])
|
||||
|
||||
# Test that the order was actually placed
|
||||
order = PurchaseOrder.objects.get(pk=1)
|
||||
self.assertEqual(order.status, PurchaseOrderStatus.PLACED)
|
||||
|
@ -11,10 +11,6 @@ from . import views
|
||||
|
||||
purchase_order_detail_urls = [
|
||||
|
||||
re_path(r'^cancel/', views.PurchaseOrderCancel.as_view(), name='po-cancel'),
|
||||
re_path(r'^issue/', views.PurchaseOrderIssue.as_view(), name='po-issue'),
|
||||
re_path(r'^complete/', views.PurchaseOrderComplete.as_view(), name='po-complete'),
|
||||
|
||||
re_path(r'^upload/', views.PurchaseOrderUpload.as_view(), name='po-upload'),
|
||||
re_path(r'^export/', views.PurchaseOrderExport.as_view(), name='po-export'),
|
||||
|
||||
@ -33,7 +29,6 @@ purchase_order_urls = [
|
||||
]
|
||||
|
||||
sales_order_detail_urls = [
|
||||
re_path(r'^cancel/', views.SalesOrderCancel.as_view(), name='so-cancel'),
|
||||
re_path(r'^export/', views.SalesOrderExport.as_view(), name='so-export'),
|
||||
|
||||
re_path(r'^.*$', views.SalesOrderDetail.as_view(), name='so-detail'),
|
||||
|
@ -30,9 +30,8 @@ from common.files import FileManager
|
||||
from . import forms as order_forms
|
||||
from part.views import PartPricing
|
||||
|
||||
from InvenTree.views import AjaxView, AjaxUpdateView
|
||||
from InvenTree.helpers import DownloadFile, str2bool
|
||||
from InvenTree.views import InvenTreeRoleMixin
|
||||
from InvenTree.helpers import DownloadFile
|
||||
from InvenTree.views import InvenTreeRoleMixin, AjaxView
|
||||
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
@ -87,123 +86,6 @@ class SalesOrderDetail(InvenTreeRoleMixin, DetailView):
|
||||
template_name = 'order/sales_order_detail.html'
|
||||
|
||||
|
||||
class PurchaseOrderCancel(AjaxUpdateView):
|
||||
""" View for cancelling a purchase order """
|
||||
|
||||
model = PurchaseOrder
|
||||
ajax_form_title = _('Cancel Order')
|
||||
ajax_template_name = 'order/order_cancel.html'
|
||||
form_class = order_forms.CancelPurchaseOrderForm
|
||||
|
||||
def validate(self, order, form, **kwargs):
|
||||
|
||||
confirm = str2bool(form.cleaned_data.get('confirm', False))
|
||||
|
||||
if not confirm:
|
||||
form.add_error('confirm', _('Confirm order cancellation'))
|
||||
|
||||
if not order.can_cancel():
|
||||
form.add_error(None, _('Order cannot be cancelled'))
|
||||
|
||||
def save(self, order, form, **kwargs):
|
||||
"""
|
||||
Cancel the PurchaseOrder
|
||||
"""
|
||||
|
||||
order.cancel_order()
|
||||
|
||||
|
||||
class SalesOrderCancel(AjaxUpdateView):
|
||||
""" View for cancelling a sales order """
|
||||
|
||||
model = SalesOrder
|
||||
ajax_form_title = _("Cancel sales order")
|
||||
ajax_template_name = "order/sales_order_cancel.html"
|
||||
form_class = order_forms.CancelSalesOrderForm
|
||||
|
||||
def validate(self, order, form, **kwargs):
|
||||
|
||||
confirm = str2bool(form.cleaned_data.get('confirm', False))
|
||||
|
||||
if not confirm:
|
||||
form.add_error('confirm', _('Confirm order cancellation'))
|
||||
|
||||
if not order.can_cancel():
|
||||
form.add_error(None, _('Order cannot be cancelled'))
|
||||
|
||||
def save(self, order, form, **kwargs):
|
||||
"""
|
||||
Once the form has been validated, cancel the SalesOrder
|
||||
"""
|
||||
|
||||
order.cancel_order()
|
||||
|
||||
|
||||
class PurchaseOrderIssue(AjaxUpdateView):
|
||||
""" View for changing a purchase order from 'PENDING' to 'ISSUED' """
|
||||
|
||||
model = PurchaseOrder
|
||||
ajax_form_title = _('Issue Order')
|
||||
ajax_template_name = "order/order_issue.html"
|
||||
form_class = order_forms.IssuePurchaseOrderForm
|
||||
|
||||
def validate(self, order, form, **kwargs):
|
||||
|
||||
confirm = str2bool(self.request.POST.get('confirm', False))
|
||||
|
||||
if not confirm:
|
||||
form.add_error('confirm', _('Confirm order placement'))
|
||||
|
||||
def save(self, order, form, **kwargs):
|
||||
"""
|
||||
Once the form has been validated, place the order.
|
||||
"""
|
||||
order.place_order()
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': _('Purchase order issued')
|
||||
}
|
||||
|
||||
|
||||
class PurchaseOrderComplete(AjaxUpdateView):
|
||||
""" View for marking a PurchaseOrder as complete.
|
||||
"""
|
||||
|
||||
form_class = order_forms.CompletePurchaseOrderForm
|
||||
model = PurchaseOrder
|
||||
ajax_template_name = "order/order_complete.html"
|
||||
ajax_form_title = _("Complete Order")
|
||||
context_object_name = 'order'
|
||||
|
||||
def get_context_data(self):
|
||||
|
||||
ctx = {
|
||||
'order': self.get_object(),
|
||||
}
|
||||
|
||||
return ctx
|
||||
|
||||
def validate(self, order, form, **kwargs):
|
||||
|
||||
confirm = str2bool(form.cleaned_data.get('confirm', False))
|
||||
|
||||
if not confirm:
|
||||
form.add_error('confirm', _('Confirm order completion'))
|
||||
|
||||
def save(self, order, form, **kwargs):
|
||||
"""
|
||||
Complete the PurchaseOrder
|
||||
"""
|
||||
|
||||
order.complete_order()
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': _('Purchase order completed')
|
||||
}
|
||||
|
||||
|
||||
class PurchaseOrderUpload(FileManagementFormView):
|
||||
''' PurchaseOrder: Upload file, match to fields and parts (using multi-Step form) '''
|
||||
|
||||
|
Reference in New Issue
Block a user