mirror of
https://github.com/inventree/InvenTree.git
synced 2025-10-23 01:17:39 +00:00
Default stock currency (#10641)
* Fix for useStockFields - Use default currency * Ensure default currency is observed * Specify field default * Improve import (for ty) * Update migration files - Point currency fields to the correct default method * Unit tests - Ensure stock item gets correct default currency * Cleaner generation of default currency value - Return empty string during migratoins * Update existing migrations * Reduce noise * Ignore "no-matching-overload" rule for ty * Tweak money_kwargs
This commit is contained in:
@@ -113,7 +113,7 @@ invalid-argument-type="ignore" # 49
|
||||
possibly-unbound-attribute="ignore" # 25 # https://github.com/astral-sh/ty/issues/164
|
||||
unknown-argument="ignore" # 3 # need to wait for betterdjango field stubs
|
||||
invalid-assignment="ignore" # 17 # need to wait for betterdjango field stubs
|
||||
|
||||
no-matching-overload="ignore" # 3 # need to wait for betterdjango field stubs
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src/backend/InvenTree", "InvenTree"]
|
||||
|
@@ -15,6 +15,8 @@ from rest_framework.fields import URLField as RestURLField
|
||||
from rest_framework.fields import empty
|
||||
|
||||
import InvenTree.helpers
|
||||
import InvenTree.ready
|
||||
from common.currency import currency_code_default
|
||||
from common.settings import get_global_setting
|
||||
|
||||
from .validators import AllowedURLValidator, allowable_url_schemes
|
||||
@@ -59,7 +61,7 @@ class InvenTreeURLField(models.URLField):
|
||||
|
||||
def money_kwargs(**kwargs):
|
||||
"""Returns the database settings for MoneyFields."""
|
||||
from common.currency import currency_code_default, currency_code_mappings
|
||||
from common.currency import currency_code_mappings
|
||||
|
||||
# Default values (if not specified)
|
||||
if 'max_digits' not in kwargs:
|
||||
@@ -71,8 +73,14 @@ def money_kwargs(**kwargs):
|
||||
if 'currency_choices' not in kwargs:
|
||||
kwargs['currency_choices'] = currency_code_mappings()
|
||||
|
||||
if 'default_currency' not in kwargs:
|
||||
kwargs['default_currency'] = currency_code_default()
|
||||
if InvenTree.ready.isRunningMigrations():
|
||||
# During migrations, avoid setting a default currency
|
||||
# This prevents issues related to early evaluation of the default currency value
|
||||
kwargs['default_currency'] = ''
|
||||
else:
|
||||
# Override default currency with a callable function
|
||||
# This ensures that the default currency is always up-to-date
|
||||
kwargs['default_currency'] = currency_code_default
|
||||
|
||||
return kwargs
|
||||
|
||||
|
@@ -9,7 +9,7 @@ from django.db.models import F, Q
|
||||
from django.urls import include, path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_filters import rest_framework as rest_filters
|
||||
import django_filters.rest_framework.filters as rest_filters
|
||||
from django_filters.rest_framework.filterset import FilterSet
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_field
|
||||
from rest_framework import serializers, status
|
||||
|
@@ -11,6 +11,7 @@ import structlog
|
||||
from moneyed import CURRENCIES
|
||||
|
||||
import InvenTree.helpers
|
||||
import InvenTree.ready
|
||||
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
@@ -19,6 +20,9 @@ def currency_code_default(create: bool = True):
|
||||
"""Returns the default currency code (or USD if not specified)."""
|
||||
from common.settings import get_global_setting
|
||||
|
||||
if InvenTree.ready.isRunningMigrations():
|
||||
return '' # pragma: no cover
|
||||
|
||||
try:
|
||||
code = get_global_setting(
|
||||
'INVENTREE_DEFAULT_CURRENCY', create=create, cache=True
|
||||
|
@@ -3,7 +3,6 @@
|
||||
from django.db import migrations, connection
|
||||
import djmoney.models.fields
|
||||
import common.currency
|
||||
import common.settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -17,11 +16,11 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='supplierpricebreak',
|
||||
name='price',
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='supplierpricebreak',
|
||||
name='price_currency',
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3),
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default='', editable=False, max_length=3),
|
||||
),
|
||||
]
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
import InvenTree.validators
|
||||
import common.currency
|
||||
import common.settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
@@ -3,7 +3,6 @@
|
||||
from django.db import migrations
|
||||
import djmoney.models.fields
|
||||
import common.currency
|
||||
import common.settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -17,11 +16,11 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='purchaseorderlineitem',
|
||||
name='purchase_price',
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='', help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='purchaseorderlineitem',
|
||||
name='purchase_price_currency',
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3),
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default='', editable=False, max_length=3),
|
||||
),
|
||||
]
|
||||
|
@@ -2,8 +2,6 @@
|
||||
|
||||
from django.db import migrations
|
||||
import djmoney.models.fields
|
||||
import common.currency
|
||||
import common.settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -16,6 +14,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='purchaseorderlineitem',
|
||||
name='purchase_price',
|
||||
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency='', help_text='Unit purchase price', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
),
|
||||
]
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
from django.db import migrations
|
||||
import common.currency
|
||||
import common.settings
|
||||
import djmoney.models.fields
|
||||
|
||||
|
||||
@@ -16,11 +15,11 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='salesorderlineitem',
|
||||
name='sale_price',
|
||||
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit sale price', max_digits=19, null=True, verbose_name='Sale Price'),
|
||||
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency='', help_text='Unit sale price', max_digits=19, null=True, verbose_name='Sale Price'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='salesorderlineitem',
|
||||
name='sale_price_currency',
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3),
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default='', editable=False, max_length=3),
|
||||
),
|
||||
]
|
||||
|
@@ -16,11 +16,11 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='partsellpricebreak',
|
||||
name='price',
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='partsellpricebreak',
|
||||
name='price_currency',
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3),
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default='', editable=False, max_length=3),
|
||||
),
|
||||
]
|
||||
|
@@ -3,7 +3,6 @@
|
||||
import InvenTree.fields
|
||||
import django.core.validators
|
||||
import common.currency
|
||||
import common.settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import djmoney.models.fields
|
||||
@@ -21,8 +20,8 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', InvenTree.fields.RoundingDecimalField(decimal_places=5, default=1, help_text='Price break quantity', max_digits=15, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Quantity')),
|
||||
('price_currency', djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default=common.currency.currency_code_default(), editable=False, max_length=3)),
|
||||
('price', djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price')),
|
||||
('price_currency', djmoney.models.fields.CurrencyField(choices=common.currency.currency_code_mappings(), default='', editable=False, max_length=3)),
|
||||
('price', djmoney.models.fields.MoneyField(decimal_places=4, default_currency='', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price')),
|
||||
('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='internalpricebreaks', to='part.part', verbose_name='Part')),
|
||||
],
|
||||
options={
|
||||
|
@@ -8,7 +8,7 @@ import djmoney.models.validators
|
||||
|
||||
import InvenTree.fields
|
||||
import common.currency
|
||||
import common.settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
|
@@ -16,11 +16,11 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='stockitem',
|
||||
name='purchase_price',
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='', help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='stockitem',
|
||||
name='purchase_price_currency',
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.all_currency_codes(), default=common.currency.currency_code_default(), editable=False, max_length=3),
|
||||
field=djmoney.models.fields.CurrencyField(choices=common.currency.all_currency_codes(), default='', editable=False, max_length=3),
|
||||
),
|
||||
]
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
from django.db import migrations
|
||||
import djmoney.models.fields
|
||||
import common.currency
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -15,6 +14,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name='stockitem',
|
||||
name='purchase_price',
|
||||
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency=common.currency.currency_code_default(), help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
field=djmoney.models.fields.MoneyField(blank=True, decimal_places=4, default_currency='', help_text='Single unit purchase price at time of purchase', max_digits=19, null=True, verbose_name='Purchase Price'),
|
||||
),
|
||||
]
|
||||
|
@@ -6,6 +6,8 @@ from django.core.exceptions import ValidationError
|
||||
from django.db.models import Sum
|
||||
from django.test import override_settings
|
||||
|
||||
from djmoney.money import Money
|
||||
|
||||
from build.models import Build
|
||||
from common.models import InvenTreeSetting
|
||||
from company.models import Company
|
||||
@@ -803,6 +805,27 @@ class StockTest(StockTestBase):
|
||||
|
||||
self.assertTrue(check_func())
|
||||
|
||||
def test_purchase_price(self):
|
||||
"""Test purchase price field."""
|
||||
from common.currency import currency_code_default
|
||||
from common.settings import set_global_setting
|
||||
|
||||
part = Part.objects.filter(virtual=False).first()
|
||||
|
||||
for currency in ['AUD', 'USD', 'JPY']:
|
||||
set_global_setting('INVENTREE_DEFAULT_CURRENCY', currency)
|
||||
self.assertEqual(currency_code_default(), currency)
|
||||
|
||||
# Create stock item, do not specify currency - should get default
|
||||
item = StockItem.objects.create(part=part, quantity=10)
|
||||
self.assertEqual(item.purchase_price_currency, currency)
|
||||
|
||||
# Create stock item, specify currency
|
||||
item = StockItem.objects.create(
|
||||
part=part, quantity=10, purchase_price=Money(5, 'GBP')
|
||||
)
|
||||
self.assertEqual(item.purchase_price_currency, 'GBP')
|
||||
|
||||
|
||||
class StockBarcodeTest(StockTestBase):
|
||||
"""Run barcode tests for the stock app."""
|
||||
|
@@ -12,8 +12,8 @@ from django.urls import include, path
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
import django_filters.rest_framework.filters as rest_filters
|
||||
import structlog
|
||||
from django_filters import rest_framework as rest_filters
|
||||
from django_filters.rest_framework.filterset import FilterSet
|
||||
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
|
||||
from rest_framework import exceptions
|
||||
|
@@ -124,10 +124,18 @@ export function useStockFields({
|
||||
}
|
||||
}, [pricing, quantity]);
|
||||
|
||||
// Set the supplier part if provided
|
||||
useEffect(() => {
|
||||
if (supplierPartId && !supplierPart) setSupplierPart(supplierPartId);
|
||||
}, [partInstance, supplierPart, supplierPartId]);
|
||||
|
||||
// Set default currency from global settings
|
||||
useEffect(() => {
|
||||
setPurchasePriceCurrency(
|
||||
globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY')
|
||||
);
|
||||
}, [globalSettings]);
|
||||
|
||||
return useMemo(() => {
|
||||
const fields: ApiFormFieldSet = {
|
||||
part: {
|
||||
@@ -248,6 +256,7 @@ export function useStockFields({
|
||||
},
|
||||
purchase_price_currency: {
|
||||
icon: <IconCoins />,
|
||||
default: globalSettings.getSetting('INVENTREE_DEFAULT_CURRENCY'),
|
||||
value: purchasePriceCurrency,
|
||||
onValueChange: (value) => {
|
||||
setPurchasePriceCurrency(value);
|
||||
|
Reference in New Issue
Block a user