mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-17 03:54:42 -06:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f48bd62534 | ||
|
|
bd92ff1290 | ||
|
|
3b3238f762 | ||
|
|
81d29efc12 | ||
|
|
044315afbe |
@@ -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():
|
||||
|
||||
@@ -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"), {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user