From 8662e6a10965e8a413568fab221d48f70be03fa7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Aug 2021 21:39:00 +1000 Subject: [PATCH 1/6] Fix a super annoying validation issue - Was throwing opaque "too many values to unpack" error - Simply needed the name of the field. --- InvenTree/InvenTree/serializers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index b156e39167..e125c234ce 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -49,7 +49,9 @@ class InvenTreeMoneySerializer(MoneyField): if amount is not None: amount = Decimal(amount) except: - raise ValidationError(_("Must be a valid number")) + raise ValidationError({ + self.field_name: _("Must be a valid number") + }) currency = data.get(get_currency_field_name(self.field_name), self.default_currency) From f96051d863a2121bf9aaa1f0fa73f89418777fb4 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Aug 2021 21:39:54 +1000 Subject: [PATCH 2/6] Replace print statement with a logger warning --- InvenTree/common/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 0e825e53b3..aed6f2bf14 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -26,6 +26,11 @@ from django.core.exceptions import ValidationError import InvenTree.helpers import InvenTree.fields +import logging + + +logger = logging.getLogger('inventree') + class BaseInvenTreeSetting(models.Model): """ @@ -1040,7 +1045,7 @@ class PriceBreak(models.Model): try: converted = convert_money(self.price, currency_code) except MissingRate: - print(f"WARNING: No currency conversion rate available for {self.price_currency} -> {currency_code}") + logger.warning(f"No currency conversion rate available for {self.price_currency} -> {currency_code}") return self.price.amount return converted.amount From d267d04bed8968c8097c5bbfaacebdb87bc43f61 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Aug 2021 21:43:31 +1000 Subject: [PATCH 3/6] Allow validation of empty money values --- InvenTree/InvenTree/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/serializers.py b/InvenTree/InvenTree/serializers.py index e125c234ce..4cea0a218c 100644 --- a/InvenTree/InvenTree/serializers.py +++ b/InvenTree/InvenTree/serializers.py @@ -46,7 +46,7 @@ class InvenTreeMoneySerializer(MoneyField): amount = None try: - if amount is not None: + if amount is not None and amount is not empty: amount = Decimal(amount) except: raise ValidationError({ From bb8b85c3752c4e97668e0e7e0e880415f55a272b Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Aug 2021 21:44:12 +1000 Subject: [PATCH 4/6] Separate purchase_price and purchase_price_currency for StockItem serializer - Add "purchase_price_string" for a read-only stringified representation - Unit testing --- InvenTree/stock/fixtures/stock.yaml | 2 + InvenTree/stock/serializers.py | 34 ++++++++++++---- InvenTree/stock/test_api.py | 61 +++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/InvenTree/stock/fixtures/stock.yaml b/InvenTree/stock/fixtures/stock.yaml index 00d3920205..25458a28e1 100644 --- a/InvenTree/stock/fixtures/stock.yaml +++ b/InvenTree/stock/fixtures/stock.yaml @@ -12,6 +12,8 @@ tree_id: 0 lft: 0 rght: 0 + purchase_price: 123 + purchase_price_currency: AUD # 5,000 screws in the bathroom - model: stock.stockitem diff --git a/InvenTree/stock/serializers.py b/InvenTree/stock/serializers.py index e7ec2fd291..535321ca80 100644 --- a/InvenTree/stock/serializers.py +++ b/InvenTree/stock/serializers.py @@ -4,6 +4,8 @@ JSON serializers for Stock app from rest_framework import serializers +from django.utils.translation import ugettext_lazy as _ + from .models import StockItem, StockLocation from .models import StockItemTracking from .models import StockItemAttachment @@ -22,9 +24,11 @@ from decimal import Decimal from datetime import datetime, timedelta import common.models +from common.settings import currency_code_default, currency_code_mappings + from company.serializers import SupplierPartSerializer from part.serializers import PartBriefSerializer -from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer +from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer, InvenTreeMoneySerializer from InvenTree.serializers import InvenTreeAttachmentSerializer, InvenTreeAttachmentSerializerField @@ -139,17 +143,28 @@ class StockItemSerializer(InvenTreeModelSerializer): required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False) - purchase_price = serializers.SerializerMethodField() + purchase_price = InvenTreeMoneySerializer( + label=_('Purchase Price'), + max_digits=19, decimal_places=4, + allow_null=True + ) + + purchase_price_currency = serializers.ChoiceField( + choices=currency_code_mappings(), + default=currency_code_default, + label=_('Currency'), + ) + + purchase_price_string = serializers.SerializerMethodField() + + def get_purchase_price_string(self, obj): + + return str(obj.purchase_price) if obj.purchase_price else '-' purchase_order_reference = serializers.CharField(source='purchase_order.reference', read_only=True) sales_order_reference = serializers.CharField(source='sales_order.reference', read_only=True) - def get_purchase_price(self, obj): - """ Return purchase_price (Money field) as string (includes currency) """ - - return str(obj.purchase_price) if obj.purchase_price else '-' - def __init__(self, *args, **kwargs): part_detail = kwargs.pop('part_detail', False) @@ -208,9 +223,12 @@ class StockItemSerializer(InvenTreeModelSerializer): 'uid', 'updated', 'purchase_price', + 'purchase_price_currency', + 'purchase_price_string', ] - """ These fields are read-only in this context. + """ + These fields are read-only in this context. They can be updated by accessing the appropriate API endpoints """ read_only_fields = [ diff --git a/InvenTree/stock/test_api.py b/InvenTree/stock/test_api.py index 74f9505c4a..4822eeaeab 100644 --- a/InvenTree/stock/test_api.py +++ b/InvenTree/stock/test_api.py @@ -428,6 +428,67 @@ class StockItemTest(StockAPITestCase): self.assertEqual(response.data['expiry_date'], expiry.isoformat()) + def test_purchase_price(self): + """ + Test that we can correctly read and adjust purchase price information via the API + """ + + url = reverse('api-stock-detail', kwargs={'pk': 1}) + + data = self.get(url, expected_code=200).data + + # Check fixture values + self.assertEqual(data['purchase_price'], '123.0000') + self.assertEqual(data['purchase_price_currency'], 'AUD') + self.assertEqual(data['purchase_price_string'], 'A$123.0000') + + # Update just the amount + data = self.patch( + url, + { + 'purchase_price': 456 + }, + expected_code=200 + ).data + + self.assertEqual(data['purchase_price'], '456.0000') + self.assertEqual(data['purchase_price_currency'], 'AUD') + + # Update the currency + data = self.patch( + url, + { + 'purchase_price_currency': 'NZD', + }, + expected_code=200 + ).data + + self.assertEqual(data['purchase_price_currency'], 'NZD') + + # Clear the price field + data = self.patch( + url, + { + 'purchase_price': None, + }, + expected_code=200 + ).data + + self.assertEqual(data['purchase_price'], None) + self.assertEqual(data['purchase_price_string'], '-') + + # Invalid currency code + data = self.patch( + url, + { + 'purchase_price_currency': 'xyz', + }, + expected_code=400 + ) + + data = self.get(url).data + self.assertEqual(data['purchase_price_currency'], 'NZD') + class StocktakeTest(StockAPITestCase): """ From 5d4f35958dacc876f55d533d97d8f389ce2b93a7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Aug 2021 21:45:32 +1000 Subject: [PATCH 5/6] Point table at the new read-only field --- InvenTree/templates/js/translated/stock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index d722f3bff8..a7f7d84595 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -1080,7 +1080,7 @@ function loadStockTable(table, options) { } }, { - field: 'purchase_price', + field: 'purchase_price_string', title: '{% trans "Purchase Price" %}', sortable: true, }, From 90d0b8b15de4c2900495679ac5c66cc8e564409f Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 23 Aug 2021 21:45:40 +1000 Subject: [PATCH 6/6] Bump API version --- InvenTree/InvenTree/version.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 7f731f2ab0..5e7453a80b 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -10,34 +10,39 @@ import common.models INVENTREE_SW_VERSION = "0.5.0 pre" -INVENTREE_API_VERSION = 9 +INVENTREE_API_VERSION = 10 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about -v9 -> 2021-08-09 +v10 -> 2021-08-23 + - Adds "purchase_price_currency" to StockItem serializer + - Adds "purchase_price_string" to StockItem serializer + - Purchase price is now writable for StockItem serializer + +v9 -> 2021-08-09 - Adds "price_string" to part pricing serializers -v8 -> 2021-07-19 +v8 -> 2021-07-19 - Refactors the API interface for SupplierPart and ManufacturerPart models - ManufacturerPart objects can no longer be created via the SupplierPart API endpoint -v7 -> 2021-07-03 +v7 -> 2021-07-03 - Introduced the concept of "API forms" in https://github.com/inventree/InvenTree/pull/1716 - API OPTIONS endpoints provide comprehensive field metedata - Multiple new API endpoints added for database models -v6 -> 2021-06-23 +v6 -> 2021-06-23 - Part and Company images can now be directly uploaded via the REST API -v5 -> 2021-06-21 +v5 -> 2021-06-21 - Adds API interface for manufacturer part parameters -v4 -> 2021-06-01 +v4 -> 2021-06-01 - BOM items can now accept "variant stock" to be assigned against them - Many slight API tweaks were needed to get this to work properly! -v3 -> 2021-05-22: +v3 -> 2021-05-22: - The updated StockItem "history tracking" now uses a different interface """