Merge pull request #1844 from eeintech/po_import

Purchase Order File Upload Update
This commit is contained in:
Oliver 2021-07-20 07:58:48 +10:00 committed by GitHub
commit 17c8dc3441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 115 additions and 76 deletions

View File

@ -20,7 +20,6 @@ from djmoney.forms.fields import MoneyField
from djmoney.models.validators import MinMoneyValidator from djmoney.models.validators import MinMoneyValidator
import InvenTree.helpers import InvenTree.helpers
import common.settings
class InvenTreeURLFormField(FormURLField): class InvenTreeURLFormField(FormURLField):
@ -42,9 +41,11 @@ class InvenTreeURLField(models.URLField):
def money_kwargs(): def money_kwargs():
""" returns the database settings for MoneyFields """ """ returns the database settings for MoneyFields """
from common.settings import currency_code_mappings, currency_code_default
kwargs = {} kwargs = {}
kwargs['currency_choices'] = common.settings.currency_code_mappings() kwargs['currency_choices'] = currency_code_mappings()
kwargs['default_currency'] = common.settings.currency_code_default kwargs['default_currency'] = currency_code_default()
return kwargs return kwargs

View File

@ -631,13 +631,34 @@ def clean_decimal(number):
""" Clean-up decimal value """ """ Clean-up decimal value """
# Check if empty # Check if empty
if number is None or number == '': if number is None or number == '' or number == 0:
return Decimal(0) return Decimal(0)
# Check if decimal type # 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
try: try:
clean_number = Decimal(number) clean_number = Decimal(number)
except InvalidOperation: except InvalidOperation:
clean_number = number # 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() return clean_number.quantize(Decimal(1)) if clean_number == clean_number.to_integral() else clean_number.normalize()

View File

@ -53,17 +53,20 @@ class FileManager:
ext = os.path.splitext(file.name)[-1].lower().replace('.', '') ext = os.path.splitext(file.name)[-1].lower().replace('.', '')
if ext in ['csv', 'tsv', ]: try:
# These file formats need string decoding if ext in ['csv', 'tsv', ]:
raw_data = file.read().decode('utf-8') # These file formats need string decoding
# Reset stream position to beginning of file raw_data = file.read().decode('utf-8')
file.seek(0) # Reset stream position to beginning of file
elif ext in ['xls', 'xlsx', 'json', 'yaml', ]: file.seek(0)
raw_data = file.read() elif ext in ['xls', 'xlsx', 'json', 'yaml', ]:
# Reset stream position to beginning of file raw_data = file.read()
file.seek(0) # Reset stream position to beginning of file
else: file.seek(0)
raise ValidationError(_(f'Unsupported file format: {ext.upper()}')) else:
raise ValidationError(_(f'Unsupported file format: {ext.upper()}'))
except UnicodeEncodeError:
raise ValidationError(_('Error reading file (invalid encoding)'))
try: try:
cleaned_data = tablib.Dataset().load(raw_data, format=ext) cleaned_data = tablib.Dataset().load(raw_data, format=ext)

View File

@ -18,8 +18,6 @@ from djmoney.settings import CURRENCY_CHOICES
from djmoney.contrib.exchange.models import convert_money from djmoney.contrib.exchange.models import convert_money
from djmoney.contrib.exchange.exceptions import MissingRate from djmoney.contrib.exchange.exceptions import MissingRate
import common.settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.validators import MinValueValidator, URLValidator from django.core.validators import MinValueValidator, URLValidator
from django.core.exceptions import ValidationError 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 MOQ (minimum order quantity) is required, bump quantity
- If order multiples are to be observed, then we need to calculate based on that, too - 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): if hasattr(instance, break_name):
price_breaks = getattr(instance, break_name).all() 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: if currency is None:
# Default currency selection # Default currency selection
currency = common.settings.currency_code_default() currency = currency_code_default()
pb_min = None pb_min = None
for pb in price_breaks: for pb in price_breaks:

View File

@ -8,15 +8,19 @@ from __future__ import unicode_literals
from moneyed import CURRENCIES from moneyed import CURRENCIES
from django.conf import settings from django.conf import settings
import common.models
def currency_code_default(): def currency_code_default():
""" """
Returns the default currency code (or USD if not specified) Returns the default currency code (or USD if not specified)
""" """
from django.db.utils import ProgrammingError
from common.models import InvenTreeSetting
code = common.models.InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY') try:
code = InvenTreeSetting.get_setting('INVENTREE_DEFAULT_CURRENCY')
except ProgrammingError:
# database is not initialized yet
code = ''
if code not in CURRENCIES: if code not in CURRENCIES:
code = 'USD' code = 'USD'
@ -42,5 +46,6 @@ def stock_expiry_enabled():
""" """
Returns True if the stock expiry feature is 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')

View File

@ -1,50 +1,74 @@
{% extends "order/order_base.html" %} {% extends "order/purchase_order_detail.html" %}
{% load inventree_extras %} {% load inventree_extras %}
{% load i18n %} {% load i18n %}
{% load static %} {% load static %}
{% block heading %} {% block menubar %}
{% trans "Upload File for Purchase Order" %} <ul class='list-group'>
{{ wizard.form.media }} <li class='list-group-item'>
<a href='#' id='po-menu-toggle'>
<span class='menu-tab-icon fas fa-expand-arrows-alt'></span>
</a>
</li>
<li class='list-group-item' title='{% trans "Return To Order" %}'>
<a href='{% url "po-detail" order.id %}' id='select-upload-file' class='nav-toggle'>
<span class='fas fa-undo side-icon'></span>
{% trans "Return To Order" %}
</a>
</li>
</ul>
{% endblock %} {% endblock %}
{% block details %} {% block page_content %}
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %} <div class='panel panel-default panel-inventree' id='panel-upload-file'>
{% if description %}- {{ description }}{% endif %}</p> <div class='panel-heading'>
{% block heading %}
<h4>{% trans "Upload File for Purchase Order" %}</h4>
{{ wizard.form.media }}
{% endblock %}
</div>
<div class='panel-content'>
{% block details %}
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
{% block form_alert %} <p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
{% endblock form_alert %} {% if description %}- {{ description }}{% endif %}</p>
<form action="" method="post" class='js-modal-form' enctype="multipart/form-data"> {% block form_alert %}
{% csrf_token %} {% endblock form_alert %}
{% load crispy_forms_tags %}
{% block form_buttons_top %} <form action="" method="post" class='js-modal-form' enctype="multipart/form-data">
{% endblock form_buttons_top %} {% csrf_token %}
{% load crispy_forms_tags %}
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'> {% block form_buttons_top %}
{{ wizard.management_form }} {% endblock form_buttons_top %}
{% block form_content %}
{% crispy wizard.form %}
{% endblock form_content %}
</table>
{% block form_buttons_bottom %} <table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
{% if wizard.steps.prev %} {{ wizard.management_form }}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button> {% block form_content %}
{% endif %} {% crispy wizard.form %}
<button type="submit" class="save btn btn-default">{% trans "Upload File" %}</button> {% endblock form_content %}
</form> </table>
{% endblock form_buttons_bottom %}
{% else %} {% block form_buttons_bottom %}
<div class='alert alert-danger alert-block' role='alert'> {% if wizard.steps.prev %}
{% trans "Order is already processed. Files cannot be uploaded." %} <button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-default">{% trans "Previous Step" %}</button>
</div> {% endif %}
{% endif %} <button type="submit" class="save btn btn-default">{% trans "Upload File" %}</button>
{% endblock details %} </form>
{% endblock form_buttons_bottom %}
{% else %}
<div class='alert alert-danger alert-block' role='alert'>
{% trans "Order is already processed. Files cannot be uploaded." %}
</div>
{% endif %}
{% endblock details %}
</div>
{% endblock %}
{% block js_ready %} {% block js_ready %}
{{ block.super }} {{ block.super }}

View File

@ -15,14 +15,6 @@
{% trans "Order Items" %} {% trans "Order Items" %}
</a> </a>
</li> </li>
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
<li class='list-group-item' title='{% trans "Upload File" %}'>
<a href='{% url "po-upload" order.id %}'>
<span class='fas fa-file-upload side-icon'></span>
{% trans "Upload File" %}
</a>
</li>
{% endif %}
<li class='list-group-item' title='{% trans "Received Stock Items" %}'> <li class='list-group-item' title='{% trans "Received Stock Items" %}'>
<a href='#' id='select-received-items' class='nav-toggle'> <a href='#' id='select-received-items' class='nav-toggle'>
<span class='fas fa-sign-in-alt side-icon'></span> <span class='fas fa-sign-in-alt side-icon'></span>

View File

@ -22,6 +22,9 @@
<button type='button' class='btn btn-primary' id='new-po-line'> <button type='button' class='btn btn-primary' id='new-po-line'>
<span class='fas fa-plus-circle'></span> {% trans "Add Line Item" %} <span class='fas fa-plus-circle'></span> {% trans "Add Line Item" %}
</button> </button>
<a class='btn btn-primary' href='{% url "po-upload" order.id %}' role='button'>
<span class='fas fa-file-upload side-icon'></span> {% trans "Upload File" %}
</a>
{% endif %} {% endif %}
</div> </div>

View File

@ -393,16 +393,7 @@ class PurchaseOrderUpload(FileManagementFormView):
p_val = row['data'][p_idx]['cell'] p_val = row['data'][p_idx]['cell']
if p_val: if p_val:
# Delete commas row['purchase_price'] = p_val
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
# Check if there is a column corresponding to "reference" # Check if there is a column corresponding to "reference"
if r_idx >= 0: if r_idx >= 0: