From 23db7a89a9ef6958169b6d3ba9833b8f640d2482 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 19 Jul 2021 14:20:54 -0400 Subject: [PATCH 1/6] Updated PO upload template, moved call to button, improved cleaned_decimal method to handle comma separator --- InvenTree/InvenTree/helpers.py | 29 ++++-- .../order/order_wizard/po_upload.html | 92 ++++++++++++------- .../order/templates/order/po_navbar.html | 8 -- .../order/purchase_order_detail.html | 3 + InvenTree/order/views.py | 11 +-- 5 files changed, 85 insertions(+), 58 deletions(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 330bd2bb68..0b091efd02 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -631,13 +631,30 @@ def clean_decimal(number): """ Clean-up decimal value """ # Check if empty - if number is None or number == '': + if number is None or number == '' or number == 0: return Decimal(0) - # Check if decimal type - try: - clean_number = Decimal(number) - except InvalidOperation: - clean_number = number + # Convert to string and remove spaces + number = str(number).replace(' ', '') + + # Guess what type of decimal and thousands separators are used + count_comma = number.count(',') + count_point = number.count('.') + + if count_comma == 1: + # Comma is used as decimal separator + if count_point > 0: + # Points are used as thousands separators: remove them + number = number.replace('.', '') + # Replace decimal separator with point + number = number.replace(',', '.') + elif count_point == 1: + # Point is used as decimal separator + if count_comma > 0: + # Commas are used as thousands separators: remove them + number = number.replace(',', '') + + # Convert to Decimal type + clean_number = Decimal(number) return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize() diff --git a/InvenTree/order/templates/order/order_wizard/po_upload.html b/InvenTree/order/templates/order/order_wizard/po_upload.html index 4357bce2c6..7c2dea1af9 100644 --- a/InvenTree/order/templates/order/order_wizard/po_upload.html +++ b/InvenTree/order/templates/order/order_wizard/po_upload.html @@ -1,50 +1,74 @@ -{% extends "order/order_base.html" %} +{% extends "order/purchase_order_detail.html" %} {% load inventree_extras %} {% load i18n %} {% load static %} -{% block heading %} -{% trans "Upload File for Purchase Order" %} -{{ wizard.form.media }} +{% block menubar %} + {% endblock %} -{% block details %} -{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %} +{% block page_content %} -

{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %} -{% if description %}- {{ description }}{% endif %}

+
+
+ {% block heading %} +

{% trans "Upload File for Purchase Order" %}

+ {{ wizard.form.media }} + {% endblock %} +
+
+ {% block details %} + {% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %} -{% block form_alert %} -{% endblock form_alert %} +

{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %} + {% if description %}- {{ description }}{% endif %}

-
-{% csrf_token %} -{% load crispy_forms_tags %} + {% block form_alert %} + {% endblock form_alert %} -{% block form_buttons_top %} -{% endblock form_buttons_top %} + + {% csrf_token %} + {% load crispy_forms_tags %} - -{{ wizard.management_form }} -{% block form_content %} -{% crispy wizard.form %} -{% endblock form_content %} -
+ {% block form_buttons_top %} + {% endblock form_buttons_top %} -{% block form_buttons_bottom %} -{% if wizard.steps.prev %} - -{% endif %} - -
-{% endblock form_buttons_bottom %} + + {{ wizard.management_form }} + {% block form_content %} + {% crispy wizard.form %} + {% endblock form_content %} +
-{% else %} - -{% endif %} -{% endblock details %} + {% block form_buttons_bottom %} + {% if wizard.steps.prev %} + + {% endif %} + + + {% endblock form_buttons_bottom %} + + {% else %} + + {% endif %} + {% endblock details %} +
+ +{% endblock %} {% block js_ready %} {{ block.super }} diff --git a/InvenTree/order/templates/order/po_navbar.html b/InvenTree/order/templates/order/po_navbar.html index 9f7967810d..bddcce4ba3 100644 --- a/InvenTree/order/templates/order/po_navbar.html +++ b/InvenTree/order/templates/order/po_navbar.html @@ -15,14 +15,6 @@ {% trans "Order Items" %} - {% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %} -
  • - - - {% trans "Upload File" %} - -
  • - {% endif %}
  • diff --git a/InvenTree/order/templates/order/purchase_order_detail.html b/InvenTree/order/templates/order/purchase_order_detail.html index 193fd75daa..b05bfa7cc2 100644 --- a/InvenTree/order/templates/order/purchase_order_detail.html +++ b/InvenTree/order/templates/order/purchase_order_detail.html @@ -22,6 +22,9 @@ + + {% trans "Upload File" %} + {% endif %}
  • diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index be1107f17b..a109b036e0 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -393,16 +393,7 @@ class PurchaseOrderUpload(FileManagementFormView): p_val = row['data'][p_idx]['cell'] if p_val: - # Delete commas - p_val = p_val.replace(',', '') - - try: - # Attempt to extract a valid decimal value from the field - purchase_price = Decimal(p_val) - # Store the 'purchase_price' value - row['purchase_price'] = purchase_price - except (ValueError, InvalidOperation): - pass + row['purchase_price'] = p_val # Check if there is a column corresponding to "reference" if r_idx >= 0: From 3ab058e84b460dc98a4aa4019595b7d79ea28cee Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 19 Jul 2021 14:49:55 -0400 Subject: [PATCH 2/6] Fixed default currency selection --- InvenTree/InvenTree/fields.py | 3 ++- InvenTree/InvenTree/helpers.py | 2 +- InvenTree/common/models.py | 5 ++--- InvenTree/common/settings.py | 8 ++++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 462d2b0e0e..65664eee6b 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -44,7 +44,7 @@ def money_kwargs(): """ returns the database settings for MoneyFields """ kwargs = {} kwargs['currency_choices'] = common.settings.currency_code_mappings() - kwargs['default_currency'] = common.settings.currency_code_default + kwargs['default_currency'] = common.settings.currency_code_default() return kwargs @@ -86,6 +86,7 @@ class InvenTreeMoneyField(MoneyField): def __init__(self, *args, **kwargs): # override initial values with the real info from database kwargs.update(money_kwargs()) + print(kwargs) super().__init__(*args, **kwargs) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 0b091efd02..374e4e5a59 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -8,7 +8,7 @@ import json import os.path from PIL import Image -from decimal import Decimal, InvalidOperation +from decimal import Decimal from wsgiref.util import FileWrapper from django.http import StreamingHttpResponse diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index 3d18d56880..dcdeea5292 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -18,8 +18,6 @@ from djmoney.settings import CURRENCY_CHOICES from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.exceptions import MissingRate -import common.settings - from django.utils.translation import ugettext_lazy as _ from django.core.validators import MinValueValidator, URLValidator from django.core.exceptions import ValidationError @@ -781,6 +779,7 @@ def get_price(instance, quantity, moq=True, multiples=True, currency=None, break - If MOQ (minimum order quantity) is required, bump quantity - If order multiples are to be observed, then we need to calculate based on that, too """ + from common.settings import currency_code_default if hasattr(instance, break_name): price_breaks = getattr(instance, break_name).all() @@ -804,7 +803,7 @@ def get_price(instance, quantity, moq=True, multiples=True, currency=None, break if currency is None: # Default currency selection - currency = common.settings.currency_code_default() + currency = currency_code_default() pb_min = None for pb in price_breaks: diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py index b34cf0b785..67dd751154 100644 --- a/InvenTree/common/settings.py +++ b/InvenTree/common/settings.py @@ -8,15 +8,14 @@ from __future__ import unicode_literals from moneyed import CURRENCIES from django.conf import settings -import common.models - def currency_code_default(): """ Returns the default currency code (or USD if not specified) """ + from common.models import InvenTreeSetting - code = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') if code not in CURRENCIES: code = 'USD' @@ -42,5 +41,6 @@ def stock_expiry_enabled(): """ Returns True if the stock expiry feature is enabled """ + from common.models import InvenTreeSetting - return common.models.InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY') + return InvenTreeSetting.get_setting('STOCK_ENABLE_EXPIRY') From c1db4c7b3daf16a7c866c0a8af30ea01f323aa49 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 19 Jul 2021 15:14:08 -0400 Subject: [PATCH 3/6] Try to catch encoding error, fixed CI --- InvenTree/InvenTree/fields.py | 7 ++++--- InvenTree/common/files.py | 25 ++++++++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 65664eee6b..81c4f7ba0b 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -20,7 +20,6 @@ from djmoney.forms.fields import MoneyField from djmoney.models.validators import MinMoneyValidator import InvenTree.helpers -import common.settings class InvenTreeURLFormField(FormURLField): @@ -42,9 +41,11 @@ class InvenTreeURLField(models.URLField): def money_kwargs(): """ returns the database settings for MoneyFields """ + from common.settings import currency_code_mappings, currency_code_default + kwargs = {} - kwargs['currency_choices'] = common.settings.currency_code_mappings() - kwargs['default_currency'] = common.settings.currency_code_default() + kwargs['currency_choices'] = currency_code_mappings() + kwargs['default_currency'] = currency_code_default() return kwargs diff --git a/InvenTree/common/files.py b/InvenTree/common/files.py index 45b6c80050..39c97dca6c 100644 --- a/InvenTree/common/files.py +++ b/InvenTree/common/files.py @@ -53,17 +53,20 @@ class FileManager: ext = os.path.splitext(file.name)[-1].lower().replace('.', '') - if ext in ['csv', 'tsv', ]: - # These file formats need string decoding - raw_data = file.read().decode('utf-8') - # Reset stream position to beginning of file - file.seek(0) - elif ext in ['xls', 'xlsx', 'json', 'yaml', ]: - raw_data = file.read() - # Reset stream position to beginning of file - file.seek(0) - else: - raise ValidationError(_(f'Unsupported file format: {ext.upper()}')) + try: + if ext in ['csv', 'tsv', ]: + # These file formats need string decoding + raw_data = file.read().decode('utf-8') + # Reset stream position to beginning of file + file.seek(0) + elif ext in ['xls', 'xlsx', 'json', 'yaml', ]: + raw_data = file.read() + # Reset stream position to beginning of file + file.seek(0) + else: + raise ValidationError(_(f'Unsupported file format: {ext.upper()}')) + except UnicodeEncodeError: + raise ValidationError(_('Error reading file (invalid encoding)')) try: cleaned_data = tablib.Dataset().load(raw_data, format=ext) From 9acd57f8e0ba5ea1996ec74dc6619e2ce9809175 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 19 Jul 2021 15:29:04 -0400 Subject: [PATCH 4/6] CI was not completely fixed --- InvenTree/InvenTree/fields.py | 1 - InvenTree/common/settings.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/fields.py b/InvenTree/InvenTree/fields.py index 81c4f7ba0b..71b31c55a8 100644 --- a/InvenTree/InvenTree/fields.py +++ b/InvenTree/InvenTree/fields.py @@ -87,7 +87,6 @@ class InvenTreeMoneyField(MoneyField): def __init__(self, *args, **kwargs): # override initial values with the real info from database kwargs.update(money_kwargs()) - print(kwargs) super().__init__(*args, **kwargs) diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py index 67dd751154..592277657f 100644 --- a/InvenTree/common/settings.py +++ b/InvenTree/common/settings.py @@ -15,7 +15,11 @@ def currency_code_default(): """ from common.models import InvenTreeSetting - code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + try: + code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') + except django.db.utils.ProgrammingError: + # database is not initialized yet + code = '' if code not in CURRENCIES: code = 'USD' From 53f2aa107a6ec44596c96ad156eedfa3323b2146 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 19 Jul 2021 15:51:04 -0400 Subject: [PATCH 5/6] Umm watch out for the true fix! --- InvenTree/common/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/common/settings.py b/InvenTree/common/settings.py index 592277657f..80f80b886f 100644 --- a/InvenTree/common/settings.py +++ b/InvenTree/common/settings.py @@ -13,11 +13,12 @@ def currency_code_default(): """ Returns the default currency code (or USD if not specified) """ + from django.db.utils import ProgrammingError from common.models import InvenTreeSetting try: code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') - except django.db.utils.ProgrammingError: + except ProgrammingError: # database is not initialized yet code = '' From 456710c5ce06ea5f5512b4732449c4a8d6a1ff64 Mon Sep 17 00:00:00 2001 From: eeintech Date: Mon, 19 Jul 2021 15:57:51 -0400 Subject: [PATCH 6/6] clean_decimal should also check if the string can be converted to Decimal type --- InvenTree/InvenTree/helpers.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 374e4e5a59..628fd2e646 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -8,7 +8,7 @@ import json import os.path from PIL import Image -from decimal import Decimal +from decimal import Decimal, InvalidOperation from wsgiref.util import FileWrapper from django.http import StreamingHttpResponse @@ -655,6 +655,10 @@ def clean_decimal(number): number = number.replace(',', '') # Convert to Decimal type - clean_number = Decimal(number) + try: + clean_number = Decimal(number) + except InvalidOperation: + # Number cannot be converted to Decimal (eg. a string containing letters) + return Decimal(0) return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()