mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +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:
parent
1fc2ef5f18
commit
4dff18e4a6
21
InvenTree/InvenTree/exchange.py
Normal file
21
InvenTree/InvenTree/exchange.py
Normal 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 {}
|
@ -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/'),
|
||||
)
|
||||
|
@ -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'),
|
||||
|
@ -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)
|
||||
|
@ -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'),
|
||||
]
|
||||
|
@ -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):
|
||||
|
18
InvenTree/common/migrations/0009_delete_currency.py
Normal file
18
InvenTree/common/migrations/0009_delete_currency.py
Normal 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',
|
||||
),
|
||||
]
|
@ -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):
|
||||
|
@ -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'
|
||||
]
|
||||
|
@ -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):
|
||||
|
@ -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<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 = [
|
||||
url(r'currency/', include(currency_urls)),
|
||||
]
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
price: 1.25
|
@ -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',
|
||||
]
|
||||
|
@ -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',
|
||||
),
|
||||
]
|
@ -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',
|
||||
),
|
||||
]
|
@ -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}'
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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 += `<div class='btn-group float-right' role='group'>`
|
||||
|
||||
|
@ -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.')
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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',
|
||||
),
|
||||
]
|
@ -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',
|
||||
),
|
||||
]
|
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
@ -10,7 +10,9 @@
|
||||
<hr>
|
||||
|
||||
<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>
|
||||
|
||||
<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,
|
||||
},
|
||||
{
|
||||
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 += `<div class='btn-group float-right' role='group'>`
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 %}
|
@ -15,9 +15,6 @@
|
||||
<li {% if tab == 'global' %} class='active' {% endif %}>
|
||||
<a href='{% url "settings-global" %}'><span class='fas fa-globe'></span> {% trans "Global" %}</a>
|
||||
</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 %}>
|
||||
<a href="{% url 'settings-part' %}"><span class='fas fa-shapes'></span> {% trans "Parts" %}</a>
|
||||
</li>
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
inventree # Install the latest version of the InvenTree API python library
|
||||
|
Loading…
Reference in New Issue
Block a user