mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	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)
			
			
This commit is contained in:
		@@ -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 %}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user