From aeb25e4c34b3ff8126a266e98d28acb086bf05d5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 08:30:14 +1000 Subject: [PATCH 1/7] startapp common --- InvenTree/common/__init__.py | 0 InvenTree/common/admin.py | 3 +++ InvenTree/common/apps.py | 5 +++++ InvenTree/common/migrations/__init__.py | 0 InvenTree/common/models.py | 3 +++ InvenTree/common/tests.py | 3 +++ InvenTree/common/views.py | 3 +++ 7 files changed, 17 insertions(+) create mode 100644 InvenTree/common/__init__.py create mode 100644 InvenTree/common/admin.py create mode 100644 InvenTree/common/apps.py create mode 100644 InvenTree/common/migrations/__init__.py create mode 100644 InvenTree/common/models.py create mode 100644 InvenTree/common/tests.py create mode 100644 InvenTree/common/views.py diff --git a/InvenTree/common/__init__.py b/InvenTree/common/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/InvenTree/common/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/InvenTree/common/apps.py b/InvenTree/common/apps.py new file mode 100644 index 0000000000..5f2f078473 --- /dev/null +++ b/InvenTree/common/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CommonConfig(AppConfig): + name = 'common' diff --git a/InvenTree/common/migrations/__init__.py b/InvenTree/common/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/InvenTree/common/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/InvenTree/common/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py new file mode 100644 index 0000000000..91ea44a218 --- /dev/null +++ b/InvenTree/common/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 7824b8561d82c6a40cd3650b3a865099480227af Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 09:07:03 +1000 Subject: [PATCH 2/7] Create a currency model --- InvenTree/InvenTree/settings.py | 1 + InvenTree/common/admin.py | 9 ++- InvenTree/common/migrations/0001_initial.py | 26 ++++++ .../migrations/0002_auto_20190902_2304.py | 17 ++++ InvenTree/common/models.py | 80 ++++++++++++++++++- Makefile | 5 +- 6 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 InvenTree/common/migrations/0001_initial.py create mode 100644 InvenTree/common/migrations/0002_auto_20190902_2304.py diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 1e993e5068..6c9d4a9b55 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -83,6 +83,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', # InvenTree apps + 'common.apps.CommonConfig', 'part.apps.PartConfig', 'stock.apps.StockConfig', 'company.apps.CompanyConfig', diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index 8c38f3f3da..faf6dd578e 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -1,3 +1,10 @@ from django.contrib import admin -# Register your models here. +from .models import Currency + + +class CurrencyAdmin(admin.ModelAdmin): + list_display = ('symbol', 'suffix', 'description', 'value', 'base') + + +admin.site.register(Currency, CurrencyAdmin) \ No newline at end of file diff --git a/InvenTree/common/migrations/0001_initial.py b/InvenTree/common/migrations/0001_initial.py new file mode 100644 index 0000000000..a3a357b86f --- /dev/null +++ b/InvenTree/common/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.4 on 2019-09-02 23:02 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Currency', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('symbol', models.CharField(help_text='Currency Symbol e.g. $', max_length=10)), + ('suffix', models.CharField(help_text='Currency Suffix e.g. AUD', max_length=10, unique=True)), + ('description', models.CharField(help_text='Currency Description', max_length=100)), + ('value', models.DecimalField(decimal_places=5, help_text='Currency Value', max_digits=10, validators=[django.core.validators.MinValueValidator(1e-05), django.core.validators.MaxValueValidator(100000)])), + ('base', models.BooleanField(default=False, help_text='Use this currency as the base currency')), + ], + ), + ] diff --git a/InvenTree/common/migrations/0002_auto_20190902_2304.py b/InvenTree/common/migrations/0002_auto_20190902_2304.py new file mode 100644 index 0000000000..a8daf16139 --- /dev/null +++ b/InvenTree/common/migrations/0002_auto_20190902_2304.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.4 on 2019-09-02 23:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='currency', + options={'verbose_name_plural': 'Currencies'}, + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 71a8362390..43bc41db34 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -1,3 +1,79 @@ -from django.db import models +""" +Common database model definitions. +These models are 'generic' and do not fit a particular business logic object. +""" -# Create your models here. +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models +from django.utils.translation import ugettext as _ +from django.core.validators import MinValueValidator, MaxValueValidator + + +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(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.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) diff --git a/Makefile b/Makefile index c898af195d..0b3379a724 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ clean: # Perform database migrations (after schema changes are made) migrate: + python3 InvenTree/manage.py makemigrations common python3 InvenTree/manage.py makemigrations company python3 InvenTree/manage.py makemigrations part python3 InvenTree/manage.py makemigrations stock @@ -40,12 +41,12 @@ style: # Run unit tests test: python3 InvenTree/manage.py check - python3 InvenTree/manage.py test build company part stock order + python3 InvenTree/manage.py test build common company order part stock # Run code coverage coverage: python3 InvenTree/manage.py check - coverage run InvenTree/manage.py test build company part stock order InvenTree + coverage run InvenTree/manage.py test build common company order part stock InvenTree coverage html # Install packages required to generate code docs From 32d09d2d37f1ae405d7892b4b8bc8da2963d1aa1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 09:10:36 +1000 Subject: [PATCH 3/7] Add default value for currency --- .../migrations/0003_auto_20190902_2310.py | 19 +++++++++++++++++++ InvenTree/common/models.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 InvenTree/common/migrations/0003_auto_20190902_2310.py diff --git a/InvenTree/common/migrations/0003_auto_20190902_2310.py b/InvenTree/common/migrations/0003_auto_20190902_2310.py new file mode 100644 index 0000000000..7bfdefa8c4 --- /dev/null +++ b/InvenTree/common/migrations/0003_auto_20190902_2310.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-09-02 23:10 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0002_auto_20190902_2304'), + ] + + operations = [ + migrations.AlterField( + model_name='currency', + name='value', + field=models.DecimalField(decimal_places=5, default=1.0, help_text='Currency Value', max_digits=10, validators=[django.core.validators.MinValueValidator(1e-05), django.core.validators.MaxValueValidator(100000)]), + ), + ] diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 43bc41db34..de3b9bab2c 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -33,7 +33,7 @@ class Currency(models.Model): description = models.CharField(max_length=100, blank=False, help_text=_('Currency Description')) - value = models.DecimalField(max_digits=10, decimal_places=5, validators=[MinValueValidator(0.00001), MaxValueValidator(100000)], help_text=_('Currency Value')) + 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')) From 9f91797f4224b805f1f3dc6683a82b2414a4da53 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 09:19:37 +1000 Subject: [PATCH 4/7] Simple test case for currency --- InvenTree/common/admin.py | 2 +- InvenTree/common/fixtures/currency.yaml | 16 ++++++++++++++++ InvenTree/common/tests.py | 18 +++++++++++++++++- InvenTree/common/views.py | 2 -- 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 InvenTree/common/fixtures/currency.yaml diff --git a/InvenTree/common/admin.py b/InvenTree/common/admin.py index faf6dd578e..1628f811a7 100644 --- a/InvenTree/common/admin.py +++ b/InvenTree/common/admin.py @@ -7,4 +7,4 @@ class CurrencyAdmin(admin.ModelAdmin): list_display = ('symbol', 'suffix', 'description', 'value', 'base') -admin.site.register(Currency, CurrencyAdmin) \ No newline at end of file +admin.site.register(Currency, CurrencyAdmin) diff --git a/InvenTree/common/fixtures/currency.yaml b/InvenTree/common/fixtures/currency.yaml new file mode 100644 index 0000000000..639b0751df --- /dev/null +++ b/InvenTree/common/fixtures/currency.yaml @@ -0,0 +1,16 @@ +# Test fixtures for Currency objects + +- model: common.currency + fields: + symbol: '$' + suffix: 'AUD' + description: 'Australian Dollars' + base: True + +- model: common.currency + fields: + symbol: '$' + suffix: 'USD' + description: 'US Dollars' + base: False + value: 1.4 \ No newline at end of file diff --git a/InvenTree/common/tests.py b/InvenTree/common/tests.py index 7ce503c2dd..5c6171a65c 100644 --- a/InvenTree/common/tests.py +++ b/InvenTree/common/tests.py @@ -1,3 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + from django.test import TestCase -# Create your tests here. +from .models import Currency + + +class CurrencyTest(TestCase): + """ Tests for Currency model """ + + fixtures = [ + 'currency', + ] + + def test_currency(self): + # Simple test for now (improve this later!) + + self.assertEqual(Currency.objects.count(), 2) diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 91ea44a218..60f00ef0ef 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -1,3 +1 @@ -from django.shortcuts import render - # Create your views here. From c6a435eba01e196ab72bf86f5e63b43f8954b586 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 09:34:32 +1000 Subject: [PATCH 5/7] Add currency field to SupplierPriceBreak --- .../0006_supplierpricebreak_currency.py | 20 +++++++++++++++++++ InvenTree/company/models.py | 16 ++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 InvenTree/company/migrations/0006_supplierpricebreak_currency.py diff --git a/InvenTree/company/migrations/0006_supplierpricebreak_currency.py b/InvenTree/company/migrations/0006_supplierpricebreak_currency.py new file mode 100644 index 0000000000..94f533cf66 --- /dev/null +++ b/InvenTree/company/migrations/0006_supplierpricebreak_currency.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.4 on 2019-09-02 23:34 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0003_auto_20190902_2310'), + ('company', '0005_auto_20190525_2356'), + ] + + operations = [ + migrations.AddField( + model_name='supplierpricebreak', + name='currency', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='common.Currency'), + ), + ] diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index 9d09557f17..eb8d7e5215 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -19,6 +19,7 @@ from django.conf import settings from django.contrib.staticfiles.templatetags.staticfiles import static from InvenTree.status_codes import OrderStatus +from common.models import Currency def rename_company_image(instance, filename): @@ -310,7 +311,7 @@ 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 - pb_cost = pb.cost + pb_cost = pb.get_cost() if pb_found: cost = pb_cost * quantity @@ -369,6 +370,7 @@ class SupplierPriceBreak(models.Model): part: Link to a SupplierPart object that this price break applies to quantity: Quantity required for price break cost: Cost at specified quantity + currency: Reference to the currency of this pricebreak (leave empty for base currency) """ part = models.ForeignKey(SupplierPart, on_delete=models.CASCADE, related_name='pricebreaks') @@ -377,6 +379,18 @@ class SupplierPriceBreak(models.Model): cost = models.DecimalField(max_digits=10, decimal_places=5, validators=[MinValueValidator(0)]) + currency = models.ForeignKey(Currency, blank=True, null=True, on_delete=models.SET_NULL) + + def get_cost(self): + """ Return the cost of this price break, converted to the base currency """ + + scaler = 1.0 + + if self.currency: + scaler = self.currency.value + + return self.cost * scaler + class Meta: unique_together = ("part", "quantity") From 09cb82cdc0bb9a5229f10b8ce1db0c82bd35f6f7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 09:46:32 +1000 Subject: [PATCH 6/7] Fix converted_cost - Incompatibility between float and decimal --- InvenTree/company/models.py | 9 ++++++--- InvenTree/company/templates/company/partdetail.html | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py index eb8d7e5215..f62b2f27a2 100644 --- a/InvenTree/company/models.py +++ b/InvenTree/company/models.py @@ -8,6 +8,7 @@ from __future__ import unicode_literals import os import math +from decimal import Decimal from django.core.validators import MinValueValidator from django.db import models @@ -311,7 +312,8 @@ 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 - pb_cost = pb.get_cost() + # Convert everything to base currency + pb_cost = pb.converted_cost if pb_found: cost = pb_cost * quantity @@ -381,10 +383,11 @@ class SupplierPriceBreak(models.Model): currency = models.ForeignKey(Currency, blank=True, null=True, on_delete=models.SET_NULL) - def get_cost(self): + @property + def converted_cost(self): """ Return the cost of this price break, converted to the base currency """ - scaler = 1.0 + scaler = Decimal(1.0) if self.currency: scaler = self.currency.value diff --git a/InvenTree/company/templates/company/partdetail.html b/InvenTree/company/templates/company/partdetail.html index d945ec31c4..00e1d03b63 100644 --- a/InvenTree/company/templates/company/partdetail.html +++ b/InvenTree/company/templates/company/partdetail.html @@ -88,7 +88,10 @@ InvenTree | {{ company.name }} - Parts {% for pb in part.price_breaks.all %} {{ pb.quantity }} - {{ pb.cost }} + + {% if pb.currency %}{{ pb.currency.symbol }}{% endif %} + {{ pb.cost }} + {% if pb.currency %}{{ pb.currency.suffix }}{% endif %}
From af8a96e08029e2dc746cfa1ecbd7a6d02be1c374 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 3 Sep 2019 09:55:15 +1000 Subject: [PATCH 7/7] Add option to edit currency --- InvenTree/company/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/company/forms.py b/InvenTree/company/forms.py index 76b1c1342c..3fec44cf63 100644 --- a/InvenTree/company/forms.py +++ b/InvenTree/company/forms.py @@ -70,5 +70,6 @@ class EditPriceBreakForm(HelperForm): fields = [ 'part', 'quantity', - 'cost' + 'cost', + 'currency', ]