From 8c3a4b60ab547ddd10fba4be9ca1a1540babf86a Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 3 Jul 2021 01:17:29 +1000 Subject: [PATCH] Refactoring forms for order line items - Required some fixes for money serializer - --- InvenTree/InvenTree/serializers.py | 42 +++++++++++++++++++ InvenTree/order/api.py | 2 +- InvenTree/order/models.py | 8 +++- InvenTree/order/serializers.py | 14 ++++++- .../templates/order/sales_order_detail.html | 18 ++++++-- .../templates/order/so_lineitem_delete.html | 6 --- InvenTree/order/urls.py | 4 -- InvenTree/order/views.py | 28 ------------- 8 files changed, 76 insertions(+), 46 deletions(-) delete mode 100644 InvenTree/order/templates/order/so_lineitem_delete.html diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index 19c70fa29a..58d33697b7 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -5,16 +5,58 @@ Serializers used in various InvenTree apps # -*- coding: utf-8 -*- from __future__ import unicode_literals + import os +from decimal import Decimal + from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError as DjangoValidationError +from django.utils.translation import ugettext_lazy as _ + +from djmoney.contrib.django_rest_framework.fields import MoneyField +from djmoney.money import Money +from djmoney.utils import MONEY_CLASSES, get_currency_field_name from rest_framework import serializers from rest_framework.utils import model_meta from rest_framework.fields import empty from rest_framework.exceptions import ValidationError +from rest_framework.serializers import DecimalField + + +class InvenTreeMoneySerializer(MoneyField): + """ + Custom serializer for 'MoneyField', + which ensures that passed values are numerically valid + + Ref: https://github.com/django-money/django-money/blob/master/djmoney/contrib/django_rest_framework/fields.py + """ + + def get_value(self, data): + """ + Test that the returned amount is a valid Decimal + """ + + amount = super(DecimalField, self).get_value(data) + + # Convert an empty string to None + if len(str(amount).strip()) == 0: + amount = None + + try: + if amount is not None: + amount = Decimal(amount) + except: + raise ValidationError(_("Must be a valid number")) + + currency = data.get(get_currency_field_name(self.field_name), self.default_currency) + + if currency and amount is not None and not isinstance(amount, MONEY_CLASSES) and amount is not empty: + return Money(amount, currency) + + return amount class UserSerializer(serializers.ModelSerializer): diff --git a/InvenTree/order/api.py b/InvenTree/order/api.py index 5337676672..04414e3db3 100644 --- a/InvenTree/order/api.py +++ b/InvenTree/order/api.py @@ -514,7 +514,7 @@ class SOLineItemList(generics.ListCreateAPIView): ] -class SOLineItemDetail(generics.RetrieveUpdateAPIView): +class SOLineItemDetail(generics.RetrieveUpdateDestroyAPIView): """ API endpoint for detail view of a SalesOrderLineItem object """ queryset = SalesOrderLineItem.objects.all() diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py index d621bcfd88..22f70d0f3c 100644 --- a/InvenTree/order/models.py +++ b/InvenTree/order/models.py @@ -698,7 +698,13 @@ class OrderLineItem(models.Model): class Meta: abstract = True - quantity = RoundingDecimalField(max_digits=15, decimal_places=5, validators=[MinValueValidator(0)], default=1, verbose_name=_('Quantity'), help_text=_('Item quantity')) + quantity = RoundingDecimalField( + verbose_name=_('Quantity'), + help_text=_('Item quantity'), + default=1, + max_digits=15, decimal_places=5, + validators=[MinValueValidator(0)], + ) reference = models.CharField(max_length=100, blank=True, verbose_name=_('Reference'), help_text=_('Line item reference')) diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py index 15e9e5ef9b..5ec7acffc5 100644 --- a/InvenTree/order/serializers.py +++ b/InvenTree/order/serializers.py @@ -16,6 +16,7 @@ from sql_util.utils import SubqueryCount import djmoney.settings from InvenTree.serializers import InvenTreeModelSerializer +from InvenTree.serializers import InvenTreeMoneySerializer from InvenTree.serializers import InvenTreeAttachmentSerializerField from company.serializers import CompanyBriefSerializer, SupplierPartSerializer @@ -124,6 +125,8 @@ class POLineItemSerializer(InvenTreeModelSerializer): part_detail = PartBriefSerializer(source='get_base_part', many=False, read_only=True) supplier_part_detail = SupplierPartSerializer(source='part', many=False, read_only=True) + purchase_price = InvenTreeMoneySerializer(max_digits=19, decimal_places=4) + purchase_price_string = serializers.CharField(source='purchase_price', read_only=True) destination = LocationBriefSerializer(source='get_destination', read_only=True) @@ -335,13 +338,20 @@ class SOLineItemSerializer(InvenTreeModelSerializer): part_detail = PartBriefSerializer(source='part', many=False, read_only=True) allocations = SalesOrderAllocationSerializer(many=True, read_only=True) - # TODO: Once https://github.com/inventree/InvenTree/issues/1687 is fixed, remove default values - quantity = serializers.FloatField(default=1) + quantity = serializers.FloatField() allocated = serializers.FloatField(source='allocated_quantity', read_only=True) fulfilled = serializers.FloatField(source='fulfilled_quantity', read_only=True) + + sale_price = InvenTreeMoneySerializer(max_digits=19, decimal_places=4) + sale_price_string = serializers.CharField(source='sale_price', read_only=True) + sale_price_currency = serializers.ChoiceField( + choices=djmoney.settings.CURRENCY_CHOICES, + help_text=_('Sale price currency'), + ) + class Meta: model = SalesOrderLineItem diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index b760409fe4..fcd352ca28 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -377,16 +377,26 @@ function setupCallbacks() { var pk = $(this).attr('pk'); - launchModalForm(`/order/sales-order/line/${pk}/edit/`, { - success: reloadTable, + constructForm(`/api/order/so-line/${pk}/`, { + fields: { + quantity: {}, + reference: {}, + sale_price: {}, + sale_price_currency: {}, + notes: {}, + }, + title: '{% trans "Edit Line Item" %}', + onSuccess: reloadTable, }); }); table.find(".button-delete").click(function() { var pk = $(this).attr('pk'); - launchModalForm(`/order/sales-order/line/${pk}/delete/`, { - success: reloadTable, + constructForm(`/api/order/so-line/${pk}/`, { + method: 'DELETE', + title: '{% trans "Delete Line Item" %}', + onSuccess: reloadTable, }); }); diff --git a/InvenTree/order/templates/order/so_lineitem_delete.html b/InvenTree/order/templates/order/so_lineitem_delete.html deleted file mode 100644 index 1d9f80d137..0000000000 --- a/InvenTree/order/templates/order/so_lineitem_delete.html +++ /dev/null @@ -1,6 +0,0 @@ -{% extends "modal_delete_form.html" %} -{% load i18n %} - -{% block pre_form_content %} -{% trans "Are you sure you wish to delete this line item?" %} -{% endblock %} \ No newline at end of file diff --git a/InvenTree/order/urls.py b/InvenTree/order/urls.py index fd1b3f89dc..c98aa52cd9 100644 --- a/InvenTree/order/urls.py +++ b/InvenTree/order/urls.py @@ -57,10 +57,6 @@ sales_order_urls = [ url(r'^line/', include([ url(r'^new/', views.SOLineItemCreate.as_view(), name='so-line-item-create'), - url(r'^(?P\d+)/', include([ - url(r'^edit/', views.SOLineItemEdit.as_view(), name='so-line-item-edit'), - url(r'^delete/', views.SOLineItemDelete.as_view(), name='so-line-item-delete'), - ])), ])), # URLs for sales order allocations diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 9e4cd2f1dd..a8048a0cf8 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -1197,34 +1197,6 @@ class SOLineItemCreate(AjaxCreateView): return ret -class SOLineItemEdit(AjaxUpdateView): - """ View for editing a SalesOrderLineItem """ - - model = SalesOrderLineItem - form_class = order_forms.EditSalesOrderLineItemForm - ajax_form_title = _('Edit Line Item') - - def get_form(self): - form = super().get_form() - - form.fields.pop('order') - form.fields.pop('part') - - return form - - -class SOLineItemDelete(AjaxDeleteView): - - model = SalesOrderLineItem - ajax_form_title = _("Delete Line Item") - ajax_template_name = "order/so_lineitem_delete.html" - - def get_data(self): - return { - 'danger': _('Deleted line item'), - } - - class SalesOrderAssignSerials(AjaxView, FormMixin): """ View for assigning stock items to a sales order,