mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #2003 from SchrodingersGat/edit-purchase-prices
Edit purchase prices
This commit is contained in:
commit
f136c974cf
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 = [
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user