diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 6844fa2ab7..966ed48f2f 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -13,6 +13,7 @@ from django.db import models from django.conf import settings import djmoney.settings +from djmoney.models.fields import MoneyField from django.utils.translation import ugettext as _ from django.core.validators import MinValueValidator, MaxValueValidator @@ -536,6 +537,15 @@ class PriceBreak(models.Model): currency = models.ForeignKey(Currency, blank=True, null=True, on_delete=models.SET_NULL) + price = MoneyField( + max_digits=19, + decimal_places=4, + default_currency='USD', + null=True, + verbose_name=_('Price'), + help_text=_('Unit price at specified quantity'), + ) + @property def symbol(self): return self.currency.symbol if self.currency else '' diff --git a/InvenTree/company/migrations/0025_auto_20201110_1001.py b/InvenTree/company/migrations/0025_auto_20201110_1001.py new file mode 100644 index 0000000000..0fd62c0a23 --- /dev/null +++ b/InvenTree/company/migrations/0025_auto_20201110_1001.py @@ -0,0 +1,24 @@ +# Generated by Django 3.0.7 on 2020-11-10 10:01 + +from django.db import migrations, connection +import djmoney.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0024_unique_name_email_constraint'), + ] + + operations = [ + migrations.AddField( + model_name='supplierpricebreak', + name='price', + field=djmoney.models.fields.MoneyField(decimal_places=4, default_currency='USD', help_text='Unit price at specified quantity', max_digits=19, null=True, verbose_name='Price'), + ), + migrations.AddField( + model_name='supplierpricebreak', + name='price_currency', + field=djmoney.models.fields.CurrencyField(choices=[('AUD', 'Australian Dollar'), ('CAD', 'Canadian Dollar'), ('EUR', 'Euro'), ('NZD', 'New Zealand Dollar'), ('GBP', 'Pound Sterling'), ('USD', 'US Dollar'), ('JPY', 'Yen')], default='USD', editable=False, max_length=3), + ), + ] diff --git a/InvenTree/company/migrations/0026_auto_20201110_1011.py b/InvenTree/company/migrations/0026_auto_20201110_1011.py new file mode 100644 index 0000000000..ffaf99842c --- /dev/null +++ b/InvenTree/company/migrations/0026_auto_20201110_1011.py @@ -0,0 +1,141 @@ +# Generated by Django 3.0.7 on 2020-11-10 10:11 + +import sys + +from moneyed import CURRENCIES +from django.db import migrations, connection +from company.models import SupplierPriceBreak + + +def migrate_currencies(apps, schema_editor): + """ + Migrate from the 'old' method of handling currencies, + to the new method which uses the django-money library. + + Previously, we created a custom Currency model, + which was very simplistic. + + Here we will attempt to map each existing "currency" reference + for the SupplierPriceBreak model, to a new django-money compatible currency. + """ + + print("Updating currency references for SupplierPriceBreak model...") + + # A list of available currency codes + currency_codes = CURRENCIES.keys() + + cursor = connection.cursor() + + # The 'suffix' field denotes the currency code + response = cursor.execute('SELECT id, suffix, description from common_currency;').fetchall() + + remap = {} + + for index, row in enumerate(response): + pk, suffix, description = row + + suffix = suffix.strip().upper() + + if suffix not in currency_codes: + print("Missing suffix:", suffix) + + while suffix not in currency_codes: + # Ask the user to input a valid currency + print(f"Could not find a valid currency matching '{suffix}'.") + print("Please enter a valid currency code") + suffix = str(input("> ")).strip() + + if pk not in remap.keys(): + remap[pk] = suffix + + # Now iterate through each SupplierPriceBreak and update the rows + response = cursor.execute('SELECT id, cost, currency_id, price, price_currency from part_supplierpricebreak;').fetchall() + + count = 0 + + for index, row in enumerate(response): + pk, cost, currency_id, price, price_currency = row + + # Copy the 'cost' field across to the 'price' field + response = cursor.execute(f'UPDATE part_supplierpricebreak set price={cost} where id={pk};') + + # Extract the updated currency code + currency_code = remap.get(currency_id, 'USD') + + # Update the currency code + response = cursor.execute(f'UPDATE part_supplierpricebreak set price_currency= "{currency_code}" where id={pk};') + + count += 1 + + print(f"Updated {count} SupplierPriceBreak rows") + +def reverse_currencies(apps, schema_editor): + """ + Reverse the "update" process. + + Here we may be in the situation that the legacy "Currency" table is empty, + and so we have to re-populate it based on the new price_currency codes. + """ + + print("Reversing currency migration...") + + cursor = connection.cursor() + + # Extract a list of currency codes which are in use + response = cursor.execute(f'SELECT id, price, price_currency from part_supplierpricebreak;').fetchall() + + codes_in_use = set() + + for index, row in enumerate(response): + pk, price, code = row + + codes_in_use.add(code) + + # Copy the 'price' field back into the 'cost' field + response = cursor.execute(f'UPDATE part_supplierpricebreak set cost={price} where id={pk};') + + # Keep a dict of which currency objects map to which code + code_map = {} + + # For each currency code in use, check if we have a matching Currency object + for code in codes_in_use: + response = cursor.execute(f'SELECT id, suffix from common_currency where suffix="{code}";') + row = response.fetchone() + + if row is not None: + # A match exists! + pk, suffix = row + code_map[suffix] = pk + else: + # No currency object exists! + description = CURRENCIES[code] + + # Create a new object in the database + print(f"Creating new Currency object for {code}") + + # Construct a query to create a new Currency object + query = f'INSERT into common_currency (symbol, suffix, description, value, base) VALUES ("$", "{code}", "{description}", 1.0, False);' + + response = cursor.execute(query) + + code_map[code] = cursor.lastrowid + + # Ok, now we know how each suffix maps to a Currency object + for suffix in code_map.keys(): + pk = code_map[suffix] + + # Update the table to point to the Currency objects + print(f"Currency {suffix} -> pk {pk}") + + response = cursor.execute(f'UPDATE part_supplierpricebreak set currency_id={pk} where price_currency="{suffix}";') + + +class Migration(migrations.Migration): + + dependencies = [ + ('company', '0025_auto_20201110_1001'), + ] + + operations = [ + migrations.RunPython(migrate_currencies, reverse_code=reverse_currencies), + ]