2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-29 12:06:44 +00:00

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!)
This commit is contained in:
Oliver Walters 2020-11-11 00:21:06 +11:00
parent 1fc2ef5f18
commit 4dff18e4a6
33 changed files with 194 additions and 422 deletions

View File

@ -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 {}

View File

@ -156,6 +156,7 @@ INSTALLED_APPS = [
'django_tex', # LaTeX output 'django_tex', # LaTeX output
'django_admin_shell', # Python shell for the admin interface 'django_admin_shell', # Python shell for the admin interface
'djmoney', # django-money integration 'djmoney', # django-money integration
'djmoney.contrib.exchange', # django-money exchange rates
] ]
LOGGING = { LOGGING = {
@ -360,6 +361,9 @@ CURRENCIES = CONFIG.get(
], ],
) )
# TODO - Allow live web-based backends in the future
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend'
LOCALE_PATHS = ( LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale/'), os.path.join(BASE_DIR, 'locale/'),
) )

View File

@ -74,7 +74,6 @@ settings_urls = [
url(r'^theme/?', ColorThemeSelectView.as_view(), name='settings-theme'), 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'^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'^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'^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'), url(r'^build/?', SettingsView.as_view(template_name='InvenTree/settings/build.html'), name='settings-build'),

View File

@ -5,11 +5,7 @@ from django.contrib import admin
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin
from .models import Currency, InvenTreeSetting from .models import InvenTreeSetting
class CurrencyAdmin(ImportExportModelAdmin):
list_display = ('symbol', 'suffix', 'description', 'value', 'base')
class SettingsAdmin(ImportExportModelAdmin): class SettingsAdmin(ImportExportModelAdmin):
@ -17,5 +13,4 @@ class SettingsAdmin(ImportExportModelAdmin):
list_display = ('key', 'value') list_display = ('key', 'value')
admin.site.register(Currency, CurrencyAdmin)
admin.site.register(InvenTreeSetting, SettingsAdmin) admin.site.register(InvenTreeSetting, SettingsAdmin)

View File

@ -5,35 +5,5 @@ Provides a JSON API for common components.
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals 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 = [ common_api_urls = [
url(r'^currency/?$', CurrencyList.as_view(), name='api-currency-list'),
] ]

View File

@ -7,21 +7,7 @@ from __future__ import unicode_literals
from InvenTree.forms import HelperForm from InvenTree.forms import HelperForm
from .models import Currency, InvenTreeSetting from .models import InvenTreeSetting
class CurrencyEditForm(HelperForm):
""" Form for creating / editing a currency object """
class Meta:
model = Currency
fields = [
'symbol',
'suffix',
'description',
'value',
'base'
]
class SettingEditForm(HelperForm): class SettingEditForm(HelperForm):

View File

@ -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',
),
]

View File

@ -7,16 +7,18 @@ These models are 'generic' and do not fit a particular business logic object.
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import decimal
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
import djmoney.settings import djmoney.settings
from djmoney.models.fields import MoneyField 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.utils.translation import ugettext as _
from django.core.validators import MinValueValidator, MaxValueValidator from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import InvenTree.helpers import InvenTree.helpers
@ -455,74 +457,6 @@ class InvenTreeSetting(models.Model):
return InvenTree.helpers.str2bool(self.value) 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): class PriceBreak(models.Model):
""" """
Represents a PriceBreak 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)]) 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( price = MoneyField(
max_digits=19, max_digits=19,
decimal_places=4, decimal_places=4,
@ -546,26 +476,21 @@ class PriceBreak(models.Model):
help_text=_('Unit price at specified quantity'), help_text=_('Unit price at specified quantity'),
) )
@property def convert_to(self, currency_code):
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):
""" """
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: return converted.amount
scaler = self.currency.value
return self.cost * scaler
class ColorTheme(models.Model): class ColorTheme(models.Model):

View File

@ -1,22 +1,3 @@
""" """
JSON serializers for common components 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'
]

View File

@ -4,7 +4,7 @@ from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from .models import Currency, InvenTreeSetting from .models import InvenTreeSetting
class CurrencyTest(TestCase): class CurrencyTest(TestCase):

View File

@ -2,17 +2,5 @@
URL lookup for common views 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<pk>\d+)/edit/', views.CurrencyEdit.as_view(), name='currency-edit'),
url(r'^(?P<pk>\d+)/delete/', views.CurrencyDelete.as_view(), name='currency-delete'),
]
common_urls = [ common_urls = [
url(r'currency/', include(currency_urls)),
] ]

View File

@ -8,37 +8,13 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.forms import CheckboxInput, Select from django.forms import CheckboxInput, Select
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import AjaxUpdateView
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from . import models from . import models
from . import forms 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): class SettingEdit(AjaxUpdateView):
""" """
View for editing an InvenTree key:value settings object, View for editing an InvenTree key:value settings object,

View File

@ -13,7 +13,6 @@ from .models import SupplierPart
from .models import SupplierPriceBreak from .models import SupplierPriceBreak
from part.models import Part from part.models import Part
from common.models import Currency
class CompanyResource(ModelResource): class CompanyResource(ModelResource):
@ -75,8 +74,6 @@ class SupplierPriceBreakResource(ModelResource):
part = Field(attribute='part', widget=widgets.ForeignKeyWidget(SupplierPart)) 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_id = Field(attribute='part__supplier__pk', readonly=True)
supplier_name = Field(attribute='part__supplier__name', readonly=True) supplier_name = Field(attribute='part__supplier__name', readonly=True)
@ -98,7 +95,7 @@ class SupplierPriceBreakAdmin(ImportExportModelAdmin):
resource_class = SupplierPriceBreakResource resource_class = SupplierPriceBreakResource
list_display = ('part', 'quantity', 'cost') list_display = ('part', 'quantity', 'price')
admin.site.register(Company, CompanyAdmin) admin.site.register(Company, CompanyAdmin)

View File

@ -7,21 +7,21 @@
fields: fields:
part: 1 part: 1
quantity: 1 quantity: 1
cost: 10 price: 10
- model: company.supplierpricebreak - model: company.supplierpricebreak
pk: 2 pk: 2
fields: fields:
part: 1 part: 1
quantity: 5 quantity: 5
cost: 7.50 price: 7.50
- model: company.supplierpricebreak - model: company.supplierpricebreak
pk: 3 pk: 3
fields: fields:
part: 1 part: 1
quantity: 25 quantity: 25
cost: 3.50 price: 3.50
# Price breaks for ACME0002 # Price breaks for ACME0002
- model: company.supplierpricebreak - model: company.supplierpricebreak
@ -29,14 +29,14 @@
fields: fields:
part: 2 part: 2
quantity: 5 quantity: 5
cost: 7.00 price: 7.00
- model: company.supplierpricebreak - model: company.supplierpricebreak
pk: 5 pk: 5
fields: fields:
part: 2 part: 2
quantity: 50 quantity: 50
cost: 1.25 price: 1.25
# Price breaks for ZERGLPHS # Price breaks for ZERGLPHS
- model: company.supplierpricebreak - model: company.supplierpricebreak
@ -44,11 +44,11 @@
fields: fields:
part: 4 part: 4
quantity: 25 quantity: 25
cost: 8 price: 8
- model: company.supplierpricebreak - model: company.supplierpricebreak
pk: 7 pk: 7
fields: fields:
part: 4 part: 4
quantity: 100 quantity: 100
cost: 1.25 price: 1.25

View File

@ -82,13 +82,10 @@ class EditPriceBreakForm(HelperForm):
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
cost = RoundingDecimalFormField(max_digits=10, decimal_places=5)
class Meta: class Meta:
model = SupplierPriceBreak model = SupplierPriceBreak
fields = [ fields = [
'part', 'part',
'quantity', 'quantity',
'cost', 'price',
'currency',
] ]

View File

@ -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',
),
]

View File

@ -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',
),
]

View File

@ -350,7 +350,7 @@ class SupplierPart(models.Model):
def unit_pricing(self): def unit_pricing(self):
return self.get_price(1) 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. """ Calculate the supplier price based on quantity price breaks.
- Don't forget to add in flat-fee cost (base_cost field) - Don't forget to add in flat-fee cost (base_cost field)
@ -372,6 +372,10 @@ class SupplierPart(models.Model):
pb_quantity = -1 pb_quantity = -1
pb_cost = 0.0 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(): for pb in self.price_breaks.all():
# Ignore this pricebreak (quantity is too high) # Ignore this pricebreak (quantity is too high)
if pb.quantity > quantity: 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 this price-break quantity is the largest so far, use it!
if pb.quantity > pb_quantity: if pb.quantity > pb_quantity:
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: if pb_found:
cost = pb_cost * quantity cost = pb_cost * quantity
@ -462,7 +467,4 @@ class SupplierPriceBreak(common.models.PriceBreak):
db_table = 'part_supplierpricebreak' db_table = 'part_supplierpricebreak'
def __str__(self): def __str__(self):
return "{mpn} - {cost} @ {quan}".format( return f'{self.part.MPN} - {self.price} @ {self.quantity}'
mpn=self.part.MPN,
cost=self.cost,
quan=self.quantity)

View File

@ -137,13 +137,9 @@ class SupplierPartSerializer(InvenTreeModelSerializer):
class SupplierPriceBreakSerializer(InvenTreeModelSerializer): class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
""" Serializer for SupplierPriceBreak object """ """ Serializer for SupplierPriceBreak object """
symbol = serializers.CharField(read_only=True)
suffix = serializers.CharField(read_only=True)
quantity = serializers.FloatField() quantity = serializers.FloatField()
cost = serializers.FloatField() price = serializers.CharField()
class Meta: class Meta:
model = SupplierPriceBreak model = SupplierPriceBreak
@ -151,8 +147,5 @@ class SupplierPriceBreakSerializer(InvenTreeModelSerializer):
'pk', 'pk',
'part', 'part',
'quantity', 'quantity',
'cost', 'price',
'currency',
'symbol',
'suffix',
] ]

View File

@ -76,18 +76,11 @@ $('#price-break-table').inventreeTable({
sortable: true, sortable: true,
}, },
{ {
field: 'cost', field: 'price',
title: '{% trans "Price" %}', title: '{% trans "Price" %}',
sortable: true, sortable: true,
formatter: function(value, row, index) { formatter: function(value, row, index) {
var html = ''; var html = value;
html += row.symbol || '';
html += value;
if (row.suffix) {
html += ' ' + row.suffix || '';
}
html += `<div class='btn-group float-right' role='group'>` html += `<div class='btn-group float-right' role='group'>`

View File

@ -6,6 +6,8 @@ from .models import Company, Contact, SupplierPart
from .models import rename_company_image from .models import rename_company_image
from part.models import Part from part.models import Part
from InvenTree.exchange import InvenTreeManualExchangeBackend
from djmoney.contrib.exchange.models import Rate
class CompanySimpleTest(TestCase): class CompanySimpleTest(TestCase):
@ -32,6 +34,14 @@ class CompanySimpleTest(TestCase):
self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS') self.zerglphs = SupplierPart.objects.get(SKU='ZERGLPHS')
self.zergm312 = SupplierPart.objects.get(SKU='ZERGM312') 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): def test_company_model(self):
c = Company.objects.get(name='ABC Co.') c = Company.objects.get(name='ABC Co.')
self.assertEqual(c.name, 'ABC Co.') self.assertEqual(c.name, 'ABC Co.')

View File

@ -12,12 +12,12 @@ from django.views.generic import DetailView, ListView, UpdateView
from django.urls import reverse from django.urls import reverse
from django.forms import HiddenInput from django.forms import HiddenInput
from moneyed import CURRENCIES
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
from InvenTree.helpers import str2bool from InvenTree.helpers import str2bool
from InvenTree.views import InvenTreeRoleMixin from InvenTree.views import InvenTreeRoleMixin
from common.models import Currency
from .models import Company from .models import Company
from .models import SupplierPart from .models import SupplierPart
from .models import SupplierPriceBreak from .models import SupplierPriceBreak
@ -29,6 +29,8 @@ from .forms import CompanyImageForm
from .forms import EditSupplierPartForm from .forms import EditSupplierPartForm
from .forms import EditPriceBreakForm from .forms import EditPriceBreakForm
import common.models
class CompanyIndex(InvenTreeRoleMixin, ListView): class CompanyIndex(InvenTreeRoleMixin, ListView):
""" View for displaying list of companies """ View for displaying list of companies
@ -435,12 +437,11 @@ class PriceBreakCreate(AjaxCreateView):
initials['part'] = self.get_part() initials['part'] = self.get_part()
# Pre-select the default currency default_currency = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
try: currency = CURRENCIES.get(default_currency, None)
base = Currency.objects.get(base=True)
initials['currency'] = base if currency is not None:
except Currency.DoesNotExist: initials['price'] = [1.0, currency]
pass
return initials return initials

View File

@ -279,7 +279,7 @@ class PartSellPriceBreakAdmin(admin.ModelAdmin):
class Meta: class Meta:
model = PartSellPriceBreak model = PartSellPriceBreak
list_display = ('part', 'quantity', 'cost', 'currency') list_display = ('part', 'quantity', 'price',)
admin.site.register(Part, PartAdmin) admin.site.register(Part, PartAdmin)

View File

@ -19,8 +19,6 @@ from .models import PartParameterTemplate, PartParameter
from .models import PartTestTemplate from .models import PartTestTemplate
from .models import PartSellPriceBreak from .models import PartSellPriceBreak
from common.models import Currency
class PartModelChoiceField(forms.ModelChoiceField): class PartModelChoiceField(forms.ModelChoiceField):
""" Extending string representation of Part instance with available stock """ """ Extending string representation of Part instance with available stock """
@ -298,13 +296,10 @@ class PartPriceForm(forms.Form):
help_text=_('Input quantity for price calculation') 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: class Meta:
model = Part model = Part
fields = [ fields = [
'quantity', 'quantity',
'currency',
] ]
@ -315,13 +310,10 @@ class EditPartSalePriceBreakForm(HelperForm):
quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5)
cost = RoundingDecimalFormField(max_digits=10, decimal_places=5)
class Meta: class Meta:
model = PartSellPriceBreak model = PartSellPriceBreak
fields = [ fields = [
'part', 'part',
'quantity', 'quantity',
'cost', 'price',
'currency',
] ]

View File

@ -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',
),
]

View File

@ -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',
),
]

View File

@ -84,13 +84,9 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
Serializer for sale prices for Part model. Serializer for sale prices for Part model.
""" """
symbol = serializers.CharField(read_only=True)
suffix = serializers.CharField(read_only=True)
quantity = serializers.FloatField() quantity = serializers.FloatField()
cost = serializers.FloatField() price = serializers.CharField()
class Meta: class Meta:
model = PartSellPriceBreak model = PartSellPriceBreak
@ -98,10 +94,7 @@ class PartSalePriceSerializer(InvenTreeModelSerializer):
'pk', 'pk',
'part', 'part',
'quantity', 'quantity',
'cost', 'price',
'currency',
'symbol',
'suffix',
] ]

View File

@ -10,7 +10,9 @@
<hr> <hr>
<div id='price-break-toolbar' class='btn-group'> <div id='price-break-toolbar' class='btn-group'>
<button class='btn btn-primary' id='new-price-break' type='button'>{% trans "Add Price Break" %}</button> <button class='btn btn-primary' id='new-price-break' type='button'>
<span class='fas fa-plus-circle'></span> {% trans "Add Price Break" %}
</button>
</div> </div>
<table class='table table-striped table-condensed' id='price-break-table' data-toolbar='#price-break-toolbar'> <table class='table table-striped table-condensed' id='price-break-table' data-toolbar='#price-break-toolbar'>
@ -81,18 +83,11 @@ $('#price-break-table').inventreeTable({
sortable: true, sortable: true,
}, },
{ {
field: 'cost', field: 'price',
title: '{% trans "Price" %}', title: '{% trans "Price" %}',
sortable: true, sortable: true,
formatter: function(value, row, index) { formatter: function(value, row, index) {
var html = ''; var html = value;
html += row.symbol || '';
html += value;
if (row.suffix) {
html += ' ' + row.suffix || '';
}
html += `<div class='btn-group float-right' role='group'>` html += `<div class='btn-group float-right' role='group'>`

View File

@ -16,6 +16,8 @@ from django.forms.models import model_to_dict
from django.forms import HiddenInput, CheckboxInput from django.forms import HiddenInput, CheckboxInput
from django.conf import settings from django.conf import settings
from moneyed import CURRENCIES
import os import os
from rapidfuzz import fuzz from rapidfuzz import fuzz
@ -28,7 +30,7 @@ from .models import match_part_names
from .models import PartTestTemplate from .models import PartTestTemplate
from .models import PartSellPriceBreak from .models import PartSellPriceBreak
from common.models import Currency, InvenTreeSetting from common.models import InvenTreeSetting
from company.models import SupplierPart from company.models import SupplierPart
from . import forms as part_forms from . import forms as part_forms
@ -1860,19 +1862,12 @@ class PartPricing(AjaxView):
if quantity < 1: if quantity < 1:
quantity = 1 quantity = 1
if currency is None: # TODO - Capacity for price comparison in different currencies
# No currency selected? Try to select a default one currency = None
try:
currency = Currency.objects.get(base=1)
except Currency.DoesNotExist:
currency = None
# Currency scaler # Currency scaler
scaler = Decimal(1.0) scaler = Decimal(1.0)
if currency is not None:
scaler = Decimal(currency.value)
part = self.get_part() part = self.get_part()
ctx = { ctx = {
@ -1942,13 +1937,8 @@ class PartPricing(AjaxView):
except ValueError: except ValueError:
quantity = 1 quantity = 1
try: # TODO - How to handle pricing in different currencies?
currency_id = int(self.request.POST.get('currency', None)) currency = None
if currency_id:
currency = Currency.objects.get(pk=currency_id)
except (ValueError, Currency.DoesNotExist):
currency = None
# Always mark the form as 'invalid' (the user may wish to keep getting pricing data) # Always mark the form as 'invalid' (the user may wish to keep getting pricing data)
data = { data = {
@ -2393,12 +2383,11 @@ class PartSalePriceBreakCreate(AjaxCreateView):
initials['part'] = self.get_part() initials['part'] = self.get_part()
# Pre-select the default currency default_currency = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
try: currency = CURRENCIES.get(default_currency, None)
base = Currency.objects.get(base=True)
initials['currency'] = base if currency is not None:
except Currency.DoesNotExist: initials['price'] = [1.0, currency]
pass
return initials return initials

View File

@ -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 %}
<h4>{% trans "Currencies" %}</h4>
<div id='currency-buttons'>
<button class='btn btn-success' id='new-currency'>
<span class='fas fa-plus-circle'></span> {% trans "New Currency" %}</button>
</div>
<table class='table table-striped table-condensed' id='currency-table' data-toolbar='#currency-buttons'>
</table>
{% 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 = "<button title='Edit Currency' class='cur-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-edit'></span></button>";
var bDel = "<button title='Delete Currency' class='cur-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='fas fa-trash-alt icon-red'></span></button>";
var html = "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
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 %}

View File

@ -15,9 +15,6 @@
<li {% if tab == 'global' %} class='active' {% endif %}> <li {% if tab == 'global' %} class='active' {% endif %}>
<a href='{% url "settings-global" %}'><span class='fas fa-globe'></span> {% trans "Global" %}</a> <a href='{% url "settings-global" %}'><span class='fas fa-globe'></span> {% trans "Global" %}</a>
</li> </li>
<li{% ifequal tab 'currency' %} class='active'{% endifequal %}>
<a href="{% url 'settings-currency' %}"><span class='fas fa-dollar-sign'></span> {% trans "Currency" %}</a>
</li>
<li{% ifequal tab 'part' %} class='active'{% endifequal %}> <li{% ifequal tab 'part' %} class='active'{% endifequal %}>
<a href="{% url 'settings-part' %}"><span class='fas fa-shapes'></span> {% trans "Parts" %}</a> <a href="{% url 'settings-part' %}"><span class='fas fa-shapes'></span> {% trans "Parts" %}</a>
</li> </li>

View File

@ -102,7 +102,6 @@ class RuleSet(models.Model):
# Models which currently do not require permissions # Models which currently do not require permissions
'common_colortheme', 'common_colortheme',
'common_currency',
'common_inventreesetting', 'common_inventreesetting',
'company_contact', 'company_contact',
'label_stockitemlabel', 'label_stockitemlabel',

View File

@ -27,5 +27,6 @@ django-weasyprint==1.0.1 # HTML PDF export
django-debug-toolbar==2.2 # Debug / profiling toolbar django-debug-toolbar==2.2 # Debug / profiling toolbar
django-admin-shell==0.1.2 # Python shell for the admin interface django-admin-shell==0.1.2 # Python shell for the admin interface
django-money==1.1 # Django app for currency management 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 inventree # Install the latest version of the InvenTree API python library