mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #1844 from eeintech/po_import
Purchase Order File Upload Update
This commit is contained in:
commit
17c8dc3441
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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')
|
||||||
|
@ -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 }}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user