Merge pull request #2003 from SchrodingersGat/edit-purchase-prices

Edit purchase prices
This commit is contained in:
Oliver 2021-08-23 22:54:12 +10:00 committed by GitHub
commit f136c974cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 20 deletions

View File

@ -46,10 +46,12 @@ class InvenTreeMoneySerializer(MoneyField):
amount = None amount = None
try: try:
if amount is not None: if amount is not None and amount is not empty:
amount = Decimal(amount) amount = Decimal(amount)
except: 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) currency = data.get(get_currency_field_name(self.field_name), self.default_currency)

View File

@ -10,34 +10,39 @@ import common.models
INVENTREE_SW_VERSION = "0.5.0 pre" 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 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 - Adds "price_string" to part pricing serializers
v8 -> 2021-07-19 v8 -> 2021-07-19
- Refactors the API interface for SupplierPart and ManufacturerPart models - Refactors the API interface for SupplierPart and ManufacturerPart models
- ManufacturerPart objects can no longer be created via the SupplierPart API endpoint - 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 - Introduced the concept of "API forms" in https://github.com/inventree/InvenTree/pull/1716
- API OPTIONS endpoints provide comprehensive field metedata - API OPTIONS endpoints provide comprehensive field metedata
- Multiple new API endpoints added for database models - 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 - 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 - 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 - BOM items can now accept "variant stock" to be assigned against them
- Many slight API tweaks were needed to get this to work properly! - 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 - The updated StockItem "history tracking" now uses a different interface
""" """

View File

@ -26,6 +26,11 @@ from django.core.exceptions import ValidationError
import InvenTree.helpers import InvenTree.helpers
import InvenTree.fields import InvenTree.fields
import logging
logger = logging.getLogger('inventree')
class BaseInvenTreeSetting(models.Model): class BaseInvenTreeSetting(models.Model):
""" """
@ -1040,7 +1045,7 @@ class PriceBreak(models.Model):
try: try:
converted = convert_money(self.price, currency_code) converted = convert_money(self.price, currency_code)
except MissingRate: 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 self.price.amount
return converted.amount return converted.amount

View File

@ -12,6 +12,8 @@
tree_id: 0 tree_id: 0
lft: 0 lft: 0
rght: 0 rght: 0
purchase_price: 123
purchase_price_currency: AUD
# 5,000 screws in the bathroom # 5,000 screws in the bathroom
- model: stock.stockitem - model: stock.stockitem

View File

@ -4,6 +4,8 @@ JSON serializers for Stock app
from rest_framework import serializers from rest_framework import serializers
from django.utils.translation import ugettext_lazy as _
from .models import StockItem, StockLocation from .models import StockItem, StockLocation
from .models import StockItemTracking from .models import StockItemTracking
from .models import StockItemAttachment from .models import StockItemAttachment
@ -22,9 +24,11 @@ from decimal import Decimal
from datetime import datetime, timedelta from datetime import datetime, timedelta
import common.models import common.models
from common.settings import currency_code_default, currency_code_mappings
from company.serializers import SupplierPartSerializer from company.serializers import SupplierPartSerializer
from part.serializers import PartBriefSerializer from part.serializers import PartBriefSerializer
from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer from InvenTree.serializers import UserSerializerBrief, InvenTreeModelSerializer, InvenTreeMoneySerializer
from InvenTree.serializers import InvenTreeAttachmentSerializer, InvenTreeAttachmentSerializerField 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) 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) purchase_order_reference = serializers.CharField(source='purchase_order.reference', read_only=True)
sales_order_reference = serializers.CharField(source='sales_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): def __init__(self, *args, **kwargs):
part_detail = kwargs.pop('part_detail', False) part_detail = kwargs.pop('part_detail', False)
@ -208,9 +223,12 @@ class StockItemSerializer(InvenTreeModelSerializer):
'uid', 'uid',
'updated', 'updated',
'purchase_price', '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 They can be updated by accessing the appropriate API endpoints
""" """
read_only_fields = [ read_only_fields = [

View File

@ -428,6 +428,67 @@ class StockItemTest(StockAPITestCase):
self.assertEqual(response.data['expiry_date'], expiry.isoformat()) 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): class StocktakeTest(StockAPITestCase):
""" """

View File

@ -1080,7 +1080,7 @@ function loadStockTable(table, options) {
} }
}, },
{ {
field: 'purchase_price', field: 'purchase_price_string',
title: '{% trans "Purchase Price" %}', title: '{% trans "Purchase Price" %}',
sortable: true, sortable: true,
}, },