Compare commits

...

5 Commits
1.0.7 ... 0.7.1

Author SHA1 Message Date
Oliver
f48bd62534 Bump version number 2022-06-02 16:35:00 +10:00
Oliver
bd92ff1290 Fix filtering for purchaseorder table on supplierpart page (#3115) 2022-06-02 14:25:28 +10:00
Oliver
3b3238f762 Check user permissions before performing search (#3083)
* Check user permissions before performing search

* JS linting

(cherry picked from commit 6c7a80c141)
2022-05-27 13:27:28 +10:00
Oliver
81d29efc12 Improve error management for order price calculation (#3075)
* Improve error management for order price calculation

- If there are missing exchange rates, it throws an error
- Very much an edge case

* Style fixes

* Add warning message if total order price cannot be calculated

* price -> cost

(cherry picked from commit 640a5d0f24)
2022-05-27 13:27:22 +10:00
Oliver Walters
044315afbe Bump version number 2022-05-24 20:33:48 +10:00
6 changed files with 141 additions and 19 deletions

View File

@@ -12,7 +12,7 @@ import common.models
from InvenTree.api_version import INVENTREE_API_VERSION
# InvenTree software version
INVENTREE_SW_VERSION = "0.7.0 dev"
INVENTREE_SW_VERSION = "0.7.1"
def inventreeInstanceName():

View File

@@ -309,7 +309,9 @@ $('#new-price-break').click(function() {
});
loadPurchaseOrderTable($("#purchase-order-table"), {
url: "{% url 'api-po-list' %}?supplier_part={{ part.id }}",
params: {
supplier_part: {{ part.id }},
}
});
loadStockTable($("#stock-table"), {

View File

@@ -4,7 +4,10 @@ Order model definitions
# -*- coding: utf-8 -*-
import logging
import os
import sys
import traceback
from datetime import datetime
from decimal import Decimal
@@ -20,7 +23,9 @@ from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from djmoney.contrib.exchange.models import convert_money
from djmoney.contrib.exchange.exceptions import MissingRate
from djmoney.money import Money
from error_report.models import Error
from markdownx.models import MarkdownxField
from mptt.models import TreeForeignKey
@@ -39,6 +44,9 @@ from stock import models as stock_models
from users import models as UserModels
logger = logging.getLogger('inventree')
def get_next_po_number():
"""
Returns the next available PurchaseOrder reference number
@@ -151,23 +159,74 @@ class Order(MetadataMixin, ReferenceIndexingMixin):
notes = MarkdownxField(blank=True, verbose_name=_('Notes'), help_text=_('Order notes'))
def get_total_price(self):
def get_total_price(self, target_currency=currency_code_default()):
"""
Calculates the total price of all order lines
Calculates the total price of all order lines, and converts to the specified target currency.
If not specified, the default system currency is used.
If currency conversion fails (e.g. there are no valid conversion rates),
then we simply return zero, rather than attempting some other calculation.
"""
target_currency = currency_code_default()
total = Money(0, target_currency)
# gather name reference
price_ref = 'sale_price' if isinstance(self, SalesOrder) else 'purchase_price'
# order items
total += sum(a.quantity * convert_money(getattr(a, price_ref), target_currency) for a in self.lines.all() if getattr(a, price_ref))
price_ref_tag = 'sale_price' if isinstance(self, SalesOrder) else 'purchase_price'
# extra lines
total += sum(a.quantity * convert_money(a.price, target_currency) for a in self.extra_lines.all() if a.price)
# order items
for line in self.lines.all():
price_ref = getattr(line, price_ref_tag)
if not price_ref:
continue
try:
total += line.quantity * convert_money(price_ref, target_currency)
except MissingRate:
# Record the error, try to press on
kind, info, data = sys.exc_info()
Error.objects.create(
kind=kind.__name__,
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)),
path='order.get_total_price',
)
logger.error(f"Missing exchange rate for '{target_currency}'")
# Return None to indicate the calculated price is invalid
return None
# extra items
for line in self.extra_lines.all():
if not line.price:
continue
try:
total += line.quantity * convert_money(line.price, target_currency)
except MissingRate:
# Record the error, try to press on
kind, info, data = sys.exc_info()
Error.objects.create(
kind=kind.__name__,
info=info,
data='\n'.join(traceback.format_exception(kind, info, data)),
path='order.get_total_price',
)
logger.error(f"Missing exchange rate for '{target_currency}'")
# Return None to indicate the calculated price is invalid
return None
# set decimal-places
total.decimal_places = 4
return total

View File

@@ -181,7 +181,15 @@ src="{% static 'img/blank_image.png' %}"
<tr>
<td><span class='fas fa-dollar-sign'></span></td>
<td>{% trans "Total cost" %}</td>
<td id="poTotalPrice">{{ order.get_total_price }}</td>
<td id="poTotalPrice">
{% with order.get_total_price as tp %}
{% if tp == None %}
<span class='badge bg-warning'>{% trans "Total cost could not be calculated" %}</span>
{% else %}
{{ tp }}
{% endif %}
{% endwith %}
</td>
</tr>
</table>
{% endblock %}

View File

@@ -188,7 +188,15 @@ src="{% static 'img/blank_image.png' %}"
<tr>
<td><span class='fas fa-dollar-sign'></span></td>
<td>{% trans "Total cost" %}</td>
<td id="soTotalPrice">{{ order.get_total_price }}</td>
<td id="soTotalPrice">
{% with order.get_total_price as tp %}
{% if tp == None %}
<span class='badge bg-warning'>{% trans "Total cost could not be calculated" %}</span>
{% else %}
{{ tp }}
{% endif %}
{% endwith %}
</td>
</tr>
</table>
{% endblock %}

View File

@@ -17,6 +17,41 @@ function closeSearchPanel() {
}
// Keep track of the roles / permissions available to the current user
var search_user_roles = null;
/*
* Check if the user has the specified role and permission
*/
function checkPermission(role, permission='view') {
if (!search_user_roles) {
return false;
}
if (!(role in search_user_roles)) {
return false;
}
var roles = search_user_roles[role];
if (!roles) {
return false;
}
var found = false;
search_user_roles[role].forEach(function(p) {
if (String(p).valueOf() == String(permission).valueOf()) {
found = true;
}
});
return found;
}
/*
* Callback when the search panel is opened.
* Ensure the panel is in a known state
@@ -27,6 +62,16 @@ function openSearchPanel() {
clearSearchResults();
// Request user roles if we do not have them
if (search_user_roles == null) {
inventreeGet('{% url "api-user-roles" %}', {}, {
success: function(response) {
search_user_roles = response.roles || {};
}
});
}
// Callback for text input changed
panel.find('#search-input').on('keyup change', searchTextChanged);
// Callback for "clear search" button
@@ -84,7 +129,7 @@ function updateSearch() {
// Show the "searching" text
$('#offcanvas-search').find('#search-pending').show();
if (user_settings.SEARCH_PREVIEW_SHOW_PARTS) {
if (checkPermission('part') && user_settings.SEARCH_PREVIEW_SHOW_PARTS) {
var params = {};
@@ -106,7 +151,7 @@ function updateSearch() {
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_CATEGORIES) {
if (checkPermission('part_category') && user_settings.SEARCH_PREVIEW_SHOW_CATEGORIES) {
// Search for matching part categories
addSearchQuery(
'category',
@@ -120,7 +165,7 @@ function updateSearch() {
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_STOCK) {
if (checkPermission('stock') && user_settings.SEARCH_PREVIEW_SHOW_STOCK) {
// Search for matching stock items
var filters = {
@@ -146,7 +191,7 @@ function updateSearch() {
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_LOCATIONS) {
if (checkPermission('stock_location') && user_settings.SEARCH_PREVIEW_SHOW_LOCATIONS) {
// Search for matching stock locations
addSearchQuery(
'location',
@@ -160,7 +205,7 @@ function updateSearch() {
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_COMPANIES) {
if ((checkPermission('sales_order') || checkPermission('purchase_order')) && user_settings.SEARCH_PREVIEW_SHOW_COMPANIES) {
// Search for matching companies
addSearchQuery(
'company',
@@ -174,7 +219,7 @@ function updateSearch() {
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS) {
if (checkPermission('purchase_order') && user_settings.SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS) {
var filters = {
supplier_detail: true,
@@ -197,7 +242,7 @@ function updateSearch() {
);
}
if (user_settings.SEARCH_PREVIEW_SHOW_SALES_ORDERS) {
if (checkPermission('sales_order') && user_settings.SEARCH_PREVIEW_SHOW_SALES_ORDERS) {
var filters = {
customer_detail: true,