From 4dff18e4a66608d0a046fae7bc8d1f3a258b9549 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 11 Nov 2020 00:21:06 +1100 Subject: [PATCH] Remove common_currency model entirely - A lot of views / pages / etc needed to be updated too - Now uses django-money fields entirely - Create a manual rate exchange backend (needs more work!) --- InvenTree/InvenTree/exchange.py | 21 ++++ InvenTree/InvenTree/settings.py | 4 + InvenTree/InvenTree/urls.py | 1 - InvenTree/common/admin.py | 7 +- InvenTree/common/api.py | 30 ----- InvenTree/common/forms.py | 16 +-- .../common/migrations/0009_delete_currency.py | 18 +++ InvenTree/common/models.py | 105 +++------------- InvenTree/common/serializers.py | 19 --- InvenTree/common/tests.py | 2 +- InvenTree/common/urls.py | 12 -- InvenTree/common/views.py | 26 +--- InvenTree/company/admin.py | 5 +- InvenTree/company/fixtures/price_breaks.yaml | 14 +-- InvenTree/company/forms.py | 5 +- ...0027_remove_supplierpricebreak_currency.py | 17 +++ .../0028_remove_supplierpricebreak_cost.py | 17 +++ InvenTree/company/models.py | 16 +-- InvenTree/company/serializers.py | 11 +- .../company/supplier_part_pricing.html | 11 +- InvenTree/company/tests.py | 10 ++ InvenTree/company/views.py | 17 +-- InvenTree/part/admin.py | 2 +- InvenTree/part/forms.py | 10 +- ...0057_remove_partsellpricebreak_currency.py | 17 +++ .../0058_remove_partsellpricebreak_cost.py | 17 +++ InvenTree/part/serializers.py | 11 +- .../part/templates/part/sale_prices.html | 15 +-- InvenTree/part/views.py | 35 ++---- .../InvenTree/settings/currency.html | 118 ------------------ .../templates/InvenTree/settings/tabs.html | 3 - InvenTree/users/models.py | 1 - requirements.txt | 3 +- 33 files changed, 194 insertions(+), 422 deletions(-) create mode 100644 InvenTree/InvenTree/exchange.py create mode 100644 InvenTree/common/migrations/0009_delete_currency.py create mode 100644 InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py create mode 100644 InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py create mode 100644 InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py create mode 100644 InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py delete mode 100644 InvenTree/templates/InvenTree/settings/currency.html diff --git a/InvenTree/InvenTree/exchange.py b/InvenTree/InvenTree/exchange.py new file mode 100644 index 0000000000..04ceabccd8 --- /dev/null +++ b/InvenTree/InvenTree/exchange.py @@ -0,0 +1,21 @@ +from djmoney.contrib.exchange.backends.base import BaseExchangeBackend + + +class InvenTreeManualExchangeBackend(BaseExchangeBackend): + """ + Backend for manually updating currency exchange rates + + See the documentation for django-money: https://github.com/django-money/django-money + + Specifically: https://github.com/django-money/django-money/tree/master/djmoney/contrib/exchange/backends + """ + + name = "inventree" + url = None + + def get_rates(self, **kwargs): + """ + Do not get any rates... + """ + + return {} diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 9c58686b56..7db24b8a9e 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -156,6 +156,7 @@ INSTALLED_APPS = [ 'django_tex', # LaTeX output 'django_admin_shell', # Python shell for the admin interface 'djmoney', # django-money integration + 'djmoney.contrib.exchange', # django-money exchange rates ] LOGGING = { @@ -360,6 +361,9 @@ CURRENCIES = CONFIG.get( ], ) +# TODO - Allow live web-based backends in the future +EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend' + LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale/'), ) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index d729210235..ff008088ab 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -74,7 +74,6 @@ settings_urls = [ url(r'^theme/?', ColorThemeSelectView.as_view(), name='settings-theme'), url(r'^global/?', SettingsView.as_view(template_name='InvenTree/settings/global.html'), name='settings-global'), - url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'), url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), url(r'^stock/?', SettingsView.as_view(template_name='InvenTree/settings/stock.html'), name='settings-stock'), url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'), diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index da12852d83..3edcd1fa8d 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -5,11 +5,7 @@ from django.contrib import admin from import_export.admin import ImportExportModelAdmin -from .models import Currency, InvenTreeSetting - - -class CurrencyAdmin(ImportExportModelAdmin): - list_display = ('symbol', 'suffix', 'description', 'value', 'base') +from .models import InvenTreeSetting class SettingsAdmin(ImportExportModelAdmin): @@ -17,5 +13,4 @@ class SettingsAdmin(ImportExportModelAdmin): list_display = ('key', 'value') -admin.site.register(Currency, CurrencyAdmin) admin.site.register(InvenTreeSetting, SettingsAdmin) diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index e1a9a0e3f0..8a2dfbd6a7 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -5,35 +5,5 @@ Provides a JSON API for common components. # -*- coding: utf-8 -*- from __future__ import unicode_literals -from rest_framework import permissions, generics, filters - -from django.conf.urls import url - -from .models import Currency -from .serializers import CurrencySerializer - - -class CurrencyList(generics.ListCreateAPIView): - """ API endpoint for accessing a list of Currency objects. - - - GET: Return a list of Currencies - - POST: Create a new currency - """ - - queryset = Currency.objects.all() - serializer_class = CurrencySerializer - - permission_classes = [ - permissions.IsAuthenticated, - ] - - filter_backends = [ - filters.OrderingFilter, - ] - - ordering_fields = ['suffix', 'value'] - - common_api_urls = [ - url(r'^currency/?$', CurrencyList.as_view(), name='api-currency-list'), ] diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py index ba6289221e..84e44f3a31 100644 --- a/InvenTree/common/forms.py +++ b/InvenTree/common/forms.py @@ -7,21 +7,7 @@ from __future__ import unicode_literals from InvenTree.forms import HelperForm -from .models import Currency, InvenTreeSetting - - -class CurrencyEditForm(HelperForm): - """ Form for creating / editing a currency object """ - - class Meta: - model = Currency - fields = [ - 'symbol', - 'suffix', - 'description', - 'value', - 'base' - ] +from .models import InvenTreeSetting class SettingEditForm(HelperForm): diff --git a/InvenTree/common/migrations/0009_delete_currency.py b/InvenTree/common/migrations/0009_delete_currency.py new file mode 100644 index 0000000000..3e2edce4c7 --- /dev/null +++ b/InvenTree/common/migrations/0009_delete_currency.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0027_remove_supplierpricebreak_currency'), + ('part', '0057_remove_partsellpricebreak_currency'), + ('common', '0008_remove_inventreesetting_description'), + ] + + operations = [ + migrations.DeleteModel( + name='Currency', + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 966ed48f2f..4c184ad392 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -7,16 +7,18 @@ These models are 'generic' and do not fit a particular business logic object. from __future__ import unicode_literals import os -import decimal from django.db import models from django.conf import settings import djmoney.settings from djmoney.models.fields import MoneyField +from djmoney.money import Money +from djmoney.contrib.exchange.models import convert_money +from djmoney.contrib.exchange.exceptions import MissingRate from django.utils.translation import ugettext as _ -from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.validators import MinValueValidator from django.core.exceptions import ValidationError import InvenTree.helpers @@ -455,74 +457,6 @@ class InvenTreeSetting(models.Model): return InvenTree.helpers.str2bool(self.value) -class Currency(models.Model): - """ - A Currency object represents a particular unit of currency. - Each Currency has a scaling factor which relates it to the base currency. - There must be one (and only one) currency which is selected as the base currency, - and each other currency is calculated relative to it. - - Attributes: - symbol: Currency symbol e.g. $ - suffix: Currency suffix e.g. AUD - description: Long-form description e.g. "Australian Dollars" - value: The value of this currency compared to the base currency. - base: True if this currency is the base currency - - """ - - symbol = models.CharField(max_length=10, blank=False, unique=False, help_text=_('Currency Symbol e.g. $')) - - suffix = models.CharField(max_length=10, blank=False, unique=True, help_text=_('Currency Suffix e.g. AUD')) - - description = models.CharField(max_length=100, blank=False, help_text=_('Currency Description')) - - value = models.DecimalField(default=1.0, max_digits=10, decimal_places=5, validators=[MinValueValidator(0.00001), MaxValueValidator(100000)], help_text=_('Currency Value')) - - base = models.BooleanField(default=False, help_text=_('Use this currency as the base currency')) - - class Meta: - verbose_name_plural = 'Currencies' - - def __str__(self): - """ Format string for currency representation """ - s = "{sym} {suf} - {desc}".format( - sym=self.symbol, - suf=self.suffix, - desc=self.description - ) - - if self.base: - s += " (Base)" - - else: - s += " = {v}".format(v=self.value) - - return s - - def save(self, *args, **kwargs): - """ Validate the model before saving - - - Ensure that there is only one base currency! - """ - - # If this currency is set as the base currency, ensure no others are - if self.base: - for cur in Currency.objects.filter(base=True).exclude(pk=self.pk): - cur.base = False - cur.save() - - # If there are no currencies set as the base currency, set this as base - if not Currency.objects.exclude(pk=self.pk).filter(base=True).exists(): - self.base = True - - # If this is the base currency, ensure value is set to unity - if self.base: - self.value = 1.0 - - super().save(*args, **kwargs) - - class PriceBreak(models.Model): """ Represents a PriceBreak model @@ -533,10 +467,6 @@ class PriceBreak(models.Model): quantity = InvenTree.fields.RoundingDecimalField(max_digits=15, decimal_places=5, default=1, validators=[MinValueValidator(1)]) - cost = InvenTree.fields.RoundingDecimalField(max_digits=10, decimal_places=5, validators=[MinValueValidator(0)]) - - currency = models.ForeignKey(Currency, blank=True, null=True, on_delete=models.SET_NULL) - price = MoneyField( max_digits=19, decimal_places=4, @@ -546,26 +476,21 @@ class PriceBreak(models.Model): help_text=_('Unit price at specified quantity'), ) - @property - def symbol(self): - return self.currency.symbol if self.currency else '' - - @property - def suffix(self): - return self.currency.suffix if self.currency else '' - - @property - def converted_cost(self): + def convert_to(self, currency_code): """ - Return the cost of this price break, converted to the base currency + Convert the unit-price at this price break to the specified currency code. + + Args: + currency_code - The currency code to convert to (e.g "USD" or "AUD") """ - scaler = decimal.Decimal(1.0) + try: + converted = convert_money(self.price, currency_code) + except MissingRate: + print(f"WARNING: No currency conversion rate available for {self.price_currency} -> {currency_code}") + return self.price.amount - if self.currency: - scaler = self.currency.value - - return self.cost * scaler + return converted.amount class ColorTheme(models.Model): diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 73b4da8adf..99ac03cdfd 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -1,22 +1,3 @@ """ JSON serializers for common components """ - -from .models import Currency - -from InvenTree.serializers import InvenTreeModelSerializer - - -class CurrencySerializer(InvenTreeModelSerializer): - """ Serializer for Currency object """ - - class Meta: - model = Currency - fields = [ - 'pk', - 'symbol', - 'suffix', - 'description', - 'value', - 'base' - ] diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 323049f164..7aa0a3a894 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals from django.test import TestCase from django.contrib.auth import get_user_model -from .models import Currency, InvenTreeSetting +from .models import InvenTreeSetting class CurrencyTest(TestCase): diff --git a/InvenTree/common/urls.py b/InvenTree/common/urls.py index b5d6deadde..261ea1a691 100644 --- a/InvenTree/common/urls.py +++ b/InvenTree/common/urls.py @@ -2,17 +2,5 @@ URL lookup for common views """ -from django.conf.urls import url, include - -from . import views - -currency_urls = [ - url(r'^new/', views.CurrencyCreate.as_view(), name='currency-create'), - - url(r'^(?P\d+)/edit/', views.CurrencyEdit.as_view(), name='currency-edit'), - url(r'^(?P\d+)/delete/', views.CurrencyDelete.as_view(), name='currency-delete'), -] - common_urls = [ - url(r'currency/', include(currency_urls)), ] diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index ff72a44e3d..3bf3769231 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -8,37 +8,13 @@ from __future__ import unicode_literals from django.utils.translation import ugettext as _ from django.forms import CheckboxInput, Select -from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView +from InvenTree.views import AjaxUpdateView from InvenTree.helpers import str2bool from . import models from . import forms -class CurrencyCreate(AjaxCreateView): - """ View for creating a new Currency object """ - - model = models.Currency - form_class = forms.CurrencyEditForm - ajax_form_title = _('Create new Currency') - - -class CurrencyEdit(AjaxUpdateView): - """ View for editing an existing Currency object """ - - model = models.Currency - form_class = forms.CurrencyEditForm - ajax_form_title = _('Edit Currency') - - -class CurrencyDelete(AjaxDeleteView): - """ View for deleting an existing Currency object """ - - model = models.Currency - ajax_form_title = _('Delete Currency') - ajax_template_name = "common/delete_currency.html" - - class SettingEdit(AjaxUpdateView): """ View for editing an InvenTree key:value settings object, diff --git a/InvenTree/company/admin.py b/InvenTree/company/admin.py index 4a802c6a41..45dd769d67 100644 --- a/InvenTree/company/admin.py +++ b/InvenTree/company/admin.py @@ -13,7 +13,6 @@ from .models import SupplierPart from .models import SupplierPriceBreak from part.models import Part -from common.models import Currency class CompanyResource(ModelResource): @@ -75,8 +74,6 @@ class SupplierPriceBreakResource(ModelResource): part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart)) - currency = Field(attribute='currency', widget=widgets.ForeignKeyWidget(Currency)) - supplier_id = Field(attribute='part__supplier__pk', readonly=True) supplier_name = Field(attribute='part__supplier__name', readonly=True) @@ -98,7 +95,7 @@ class SupplierPriceBreakAdmin(ImportExportModelAdmin): resource_class = SupplierPriceBreakResource - list_display = ('part', 'quantity', 'cost') + list_display = ('part', 'quantity', 'price') admin.site.register(Company, CompanyAdmin) diff --git a/InvenTree/company/fixtures/price_breaks.yaml b/InvenTree/company/fixtures/price_breaks.yaml index 6ae8cce94c..dbcbacb017 100644 --- a/InvenTree/company/fixtures/price_breaks.yaml +++ b/InvenTree/company/fixtures/price_breaks.yaml @@ -7,21 +7,21 @@ fields: part: 1 quantity: 1 - cost: 10 + price: 10 - model: company.supplierpricebreak pk: 2 fields: part: 1 quantity: 5 - cost: 7.50 + price: 7.50 - model: company.supplierpricebreak pk: 3 fields: part: 1 quantity: 25 - cost: 3.50 + price: 3.50 # Price breaks for ACME0002 - model: company.supplierpricebreak @@ -29,14 +29,14 @@ fields: part: 2 quantity: 5 - cost: 7.00 + price: 7.00 - model: company.supplierpricebreak pk: 5 fields: part: 2 quantity: 50 - cost: 1.25 + price: 1.25 # Price breaks for ZERGLPHS - model: company.supplierpricebreak @@ -44,11 +44,11 @@ fields: part: 4 quantity: 25 - cost: 8 + price: 8 - model: company.supplierpricebreak pk: 7 fields: part: 4 quantity: 100 - cost: 1.25 \ No newline at end of file + price: 1.25 \ No newline at end of file diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index ac3cc69c99..9ebb8839f3 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -82,13 +82,10 @@ class EditPriceBreakForm(HelperForm): quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) - cost = RoundingDecimalFormField(max_digits=10, decimal_places=5) - class Meta: model = SupplierPriceBreak fields = [ 'part', 'quantity', - 'cost', - 'currency', + 'price', ] diff --git a/InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py b/InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py new file mode 100644 index 0000000000..b2e23d7538 --- /dev/null +++ b/InvenTree/company/migrations/0027_remove_supplierpricebreak_currency.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0026_auto_20201110_1011'), + ] + + operations = [ + migrations.RemoveField( + model_name='supplierpricebreak', + name='currency', + ), + ] diff --git a/InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py b/InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py new file mode 100644 index 0000000000..0522560f26 --- /dev/null +++ b/InvenTree/company/migrations/0028_remove_supplierpricebreak_cost.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0027_remove_supplierpricebreak_currency'), + ] + + operations = [ + migrations.RemoveField( + model_name='supplierpricebreak', + name='cost', + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index b9fed2ee7b..75b765bb2e 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -350,7 +350,7 @@ class SupplierPart(models.Model): def unit_pricing(self): return self.get_price(1) - def get_price(self, quantity, moq=True, multiples=True): + def get_price(self, quantity, moq=True, multiples=True, currency=None): """ Calculate the supplier price based on quantity price breaks. - Don't forget to add in flat-fee cost (base_cost field) @@ -372,6 +372,10 @@ class SupplierPart(models.Model): pb_quantity = -1 pb_cost = 0.0 + if currency is None: + # Default currency selection + currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + for pb in self.price_breaks.all(): # Ignore this pricebreak (quantity is too high) if pb.quantity > quantity: @@ -382,8 +386,9 @@ class SupplierPart(models.Model): # If this price-break quantity is the largest so far, use it! if pb.quantity > pb_quantity: pb_quantity = pb.quantity - # Convert everything to base currency - pb_cost = pb.converted_cost + + # Convert everything to the selected currency + pb_cost = pb.convert_to(currency) if pb_found: cost = pb_cost * quantity @@ -462,7 +467,4 @@ class SupplierPriceBreak(common.models.PriceBreak): db_table = 'part_supplierpricebreak' def __str__(self): - return "{mpn} - {cost} @ {quan}".format( - mpn=self.part.MPN, - cost=self.cost, - quan=self.quantity) + return f'{self.part.MPN} - {self.price} @ {self.quantity}' diff --git a/InvenTree/company/serializers.py b/InvenTree/company/serializers.py index f6de7d4f50..4951bd3ad0 100644 --- a/InvenTree/company/serializers.py +++ b/InvenTree/company/serializers.py @@ -137,13 +137,9 @@ class SupplierPartSerializer(InvenTreeModelSerializer): class SupplierPriceBreakSerializer(InvenTreeModelSerializer): """ Serializer for SupplierPriceBreak object """ - symbol = serializers.CharField(read_only=True) - - suffix = serializers.CharField(read_only=True) - quantity = serializers.FloatField() - cost = serializers.FloatField() + price = serializers.CharField() class Meta: model = SupplierPriceBreak @@ -151,8 +147,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer): 'pk', 'part', 'quantity', - 'cost', - 'currency', - 'symbol', - 'suffix', + 'price', ] diff --git a/InvenTree/company/templates/company/supplier_part_pricing.html b/InvenTree/company/templates/company/supplier_part_pricing.html index 6138669bc4..e665339968 100644 --- a/InvenTree/company/templates/company/supplier_part_pricing.html +++ b/InvenTree/company/templates/company/supplier_part_pricing.html @@ -76,18 +76,11 @@ $('#price-break-table').inventreeTable({ sortable: true, }, { - field: 'cost', + field: 'price', title: '{% trans "Price" %}', sortable: true, formatter: function(value, row, index) { - var html = ''; - - html += row.symbol || ''; - html += value; - - if (row.suffix) { - html += ' ' + row.suffix || ''; - } + var html = value; html += `
` diff --git a/InvenTree/company/tests.py b/InvenTree/company/tests.py index db515d3e59..1e57adcda3 100644 --- a/InvenTree/company/tests.py +++ b/InvenTree/company/tests.py @@ -6,6 +6,8 @@ from .models import Company, Contact, SupplierPart from .models import rename_company_image from part.models import Part +from InvenTree.exchange import InvenTreeManualExchangeBackend +from djmoney.contrib.exchange.models import Rate class CompanySimpleTest(TestCase): @@ -32,6 +34,14 @@ class CompanySimpleTest(TestCase): self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS') self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312') + InvenTreeManualExchangeBackend().update_rates() + + Rate.objects.create( + currency='AUD', + value='1.35', + backend_id='inventree', + ) + def test_company_model(self): c = Company.objects.get(name='ABC Co.') self.assertEqual(c.name, 'ABC Co.') diff --git a/InvenTree/company/views.py b/InvenTree/company/views.py index dce341d184..abdad55322 100644 --- a/InvenTree/company/views.py +++ b/InvenTree/company/views.py @@ -12,12 +12,12 @@ from django.views.generic import DetailView, ListView, UpdateView from django.urls import reverse from django.forms import HiddenInput +from moneyed import CURRENCIES + from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.helpers import str2bool from InvenTree.views import InvenTreeRoleMixin -from common.models import Currency - from .models import Company from .models import SupplierPart from .models import SupplierPriceBreak @@ -29,6 +29,8 @@ from .forms import CompanyImageForm from .forms import EditSupplierPartForm from .forms import EditPriceBreakForm +import common.models + class CompanyIndex(InvenTreeRoleMixin, ListView): """ View for displaying list of companies @@ -435,12 +437,11 @@ class PriceBreakCreate(AjaxCreateView): initials['part'] = self.get_part() - # Pre-select the default currency - try: - base = Currency.objects.get(base=True) - initials['currency'] = base - except Currency.DoesNotExist: - pass + default_currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + currency = CURRENCIES.get(default_currency, None) + + if currency is not None: + initials['price'] = [1.0, currency] return initials diff --git a/InvenTree/part/admin.py b/InvenTree/part/admin.py index 7476197547..11dd5c8dc0 100644 --- a/InvenTree/part/admin.py +++ b/InvenTree/part/admin.py @@ -279,7 +279,7 @@ class PartSellPriceBreakAdmin(admin.ModelAdmin): class Meta: model = PartSellPriceBreak - list_display = ('part', 'quantity', 'cost', 'currency') + list_display = ('part', 'quantity', 'price',) admin.site.register(Part, PartAdmin) diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index d72df4ed9f..c100b08b3e 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -19,8 +19,6 @@ from .models import PartParameterTemplate, PartParameter from .models import PartTestTemplate from .models import PartSellPriceBreak -from common.models import Currency - class PartModelChoiceField(forms.ModelChoiceField): """ Extending string representation of Part instance with available stock """ @@ -298,13 +296,10 @@ class PartPriceForm(forms.Form): help_text=_('Input quantity for price calculation') ) - currency = forms.ModelChoiceField(queryset=Currency.objects.all(), label='Currency', help_text=_('Select currency for price calculation')) - class Meta: model = Part fields = [ 'quantity', - 'currency', ] @@ -315,13 +310,10 @@ class EditPartSalePriceBreakForm(HelperForm): quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) - cost = RoundingDecimalFormField(max_digits=10, decimal_places=5) - class Meta: model = PartSellPriceBreak fields = [ 'part', 'quantity', - 'cost', - 'currency', + 'price', ] diff --git a/InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py b/InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py new file mode 100644 index 0000000000..974aecef4b --- /dev/null +++ b/InvenTree/part/migrations/0057_remove_partsellpricebreak_currency.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:40 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0056_auto_20201110_1125'), + ] + + operations = [ + migrations.RemoveField( + model_name='partsellpricebreak', + name='currency', + ), + ] diff --git a/InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py b/InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py new file mode 100644 index 0000000000..dcf625aa6f --- /dev/null +++ b/InvenTree/part/migrations/0058_remove_partsellpricebreak_cost.py @@ -0,0 +1,17 @@ +# Generated by Django 3.0.7 on 2020-11-10 11:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0057_remove_partsellpricebreak_currency'), + ] + + operations = [ + migrations.RemoveField( + model_name='partsellpricebreak', + name='cost', + ), + ] diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index f6eb8dc95b..14a1147b67 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -84,13 +84,9 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): Serializer for sale prices for Part model. """ - symbol = serializers.CharField(read_only=True) - - suffix = serializers.CharField(read_only=True) - quantity = serializers.FloatField() - cost = serializers.FloatField() + price = serializers.CharField() class Meta: model = PartSellPriceBreak @@ -98,10 +94,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer): 'pk', 'part', 'quantity', - 'cost', - 'currency', - 'symbol', - 'suffix', + 'price', ] diff --git a/InvenTree/part/templates/part/sale_prices.html b/InvenTree/part/templates/part/sale_prices.html index 8d3cc61afd..033f280da8 100644 --- a/InvenTree/part/templates/part/sale_prices.html +++ b/InvenTree/part/templates/part/sale_prices.html @@ -10,7 +10,9 @@
- +
@@ -81,18 +83,11 @@ $('#price-break-table').inventreeTable({ sortable: true, }, { - field: 'cost', + field: 'price', title: '{% trans "Price" %}', sortable: true, formatter: function(value, row, index) { - var html = ''; - - html += row.symbol || ''; - html += value; - - if (row.suffix) { - html += ' ' + row.suffix || ''; - } + var html = value; html += `
` diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 2504b056c5..9b5aed0ddb 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -16,6 +16,8 @@ from django.forms.models import model_to_dict from django.forms import HiddenInput, CheckboxInput from django.conf import settings +from moneyed import CURRENCIES + import os from rapidfuzz import fuzz @@ -28,7 +30,7 @@ from .models import match_part_names from .models import PartTestTemplate from .models import PartSellPriceBreak -from common.models import Currency, InvenTreeSetting +from common.models import InvenTreeSetting from company.models import SupplierPart from . import forms as part_forms @@ -1860,19 +1862,12 @@ class PartPricing(AjaxView): if quantity < 1: quantity = 1 - if currency is None: - # No currency selected? Try to select a default one - try: - currency = Currency.objects.get(base=1) - except Currency.DoesNotExist: - currency = None + # TODO - Capacity for price comparison in different currencies + currency = None # Currency scaler scaler = Decimal(1.0) - if currency is not None: - scaler = Decimal(currency.value) - part = self.get_part() ctx = { @@ -1942,13 +1937,8 @@ class PartPricing(AjaxView): except ValueError: quantity = 1 - try: - currency_id = int(self.request.POST.get('currency', None)) - - if currency_id: - currency = Currency.objects.get(pk=currency_id) - except (ValueError, Currency.DoesNotExist): - currency = None + # TODO - How to handle pricing in different currencies? + currency = None # Always mark the form as 'invalid' (the user may wish to keep getting pricing data) data = { @@ -2393,12 +2383,11 @@ class PartSalePriceBreakCreate(AjaxCreateView): initials['part'] = self.get_part() - # Pre-select the default currency - try: - base = Currency.objects.get(base=True) - initials['currency'] = base - except Currency.DoesNotExist: - pass + default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + currency = CURRENCIES.get(default_currency, None) + + if currency is not None: + initials['price'] = [1.0, currency] return initials diff --git a/InvenTree/templates/InvenTree/settings/currency.html b/InvenTree/templates/InvenTree/settings/currency.html deleted file mode 100644 index 444dd96c66..0000000000 --- a/InvenTree/templates/InvenTree/settings/currency.html +++ /dev/null @@ -1,118 +0,0 @@ -{% extends "InvenTree/settings/settings.html" %} -{% load i18n %} - -{% block subtitle %} -{% trans "General Settings" %} -{% endblock %} - -{% block tabs %} -{% include "InvenTree/settings/tabs.html" with tab='currency' %} -{% endblock %} - -{% block settings %} - -

{% trans "Currencies" %}

- -
- -
- -
-
-{% endblock %} - -{% block js_ready %} -{{ block.super }} - - $("#currency-table").inventreeTable({ - url: "{% url 'api-currency-list' %}", - queryParams: { - ordering: 'suffix' - }, - formatNoMatches: function() { return "No currencies found"; }, - rowStyle: function(row, index) { - if (row.base) { - return {classes: 'basecurrency'}; - } else { - return {}; - } - }, - columns: [ - { - field: 'pk', - title: 'ID', - visible: false, - switchable: false, - }, - { - field: 'symbol', - title: 'Symbol', - }, - { - field: 'suffix', - title: 'Currency', - sortable: true, - }, - { - field: 'description', - title: 'Description', - sortable: true, - }, - { - field: 'value', - title: 'Value', - sortable: true, - formatter: function(value, row, index, field) { - if (row.base) { - return "Base Currency"; - } else { - return value; - } - } - }, - { - formatter: function(value, row, index, field) { - - var bEdit = ""; - var bDel = ""; - - var html = "
" + bEdit + bDel + "
"; - - return html; - } - } - ] - }); - - $("#currency-table").on('click', '.cur-edit', function() { - var button = $(this); - var url = "/common/currency/" + button.attr('pk') + "/edit/"; - - launchModalForm(url, { - success: function() { - $("#currency-table").bootstrapTable('refresh'); - }, - }); - }); - - $("#currency-table").on('click', '.cur-delete', function() { - var button = $(this); - var url = "/common/currency/" + button.attr('pk') + "/delete/"; - - launchModalForm(url, { - success: function() { - $("#currency-table").bootstrapTable('refresh'); - }, - }); - }); - - $("#new-currency").click(function() { - launchModalForm("{% url 'currency-create' %}", { - success: function() { - $("#currency-table").bootstrapTable('refresh'); - }, - }); - }); - -{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html index d104908c49..a278c56325 100644 --- a/InvenTree/templates/InvenTree/settings/tabs.html +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -15,9 +15,6 @@
  • {% trans "Global" %}
  • - - {% trans "Currency" %} - {% trans "Parts" %} diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 98efb14764..a5b9021807 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -102,7 +102,6 @@ class RuleSet(models.Model): # Models which currently do not require permissions 'common_colortheme', - 'common_currency', 'common_inventreesetting', 'company_contact', 'label_stockitemlabel', diff --git a/requirements.txt b/requirements.txt index bc5fff38cc..9823b9d1fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,5 +27,6 @@ django-weasyprint==1.0.1 # HTML PDF export django-debug-toolbar==2.2 # Debug / profiling toolbar django-admin-shell==0.1.2 # Python shell for the admin interface django-money==1.1 # Django app for currency management +certifi # Certifi is (most likely) installed through one of the requirements above -inventree # Install the latest version of the InvenTree API python library \ No newline at end of file +inventree # Install the latest version of the InvenTree API python library