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 -*-
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import traceback
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime
 | 
				
			||||||
from decimal import Decimal
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,7 +23,9 @@ from django.urls import reverse
 | 
				
			|||||||
from django.utils.translation import gettext_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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.money import Money
 | 
					from djmoney.money import Money
 | 
				
			||||||
 | 
					from error_report.models import Error
 | 
				
			||||||
from markdownx.models import MarkdownxField
 | 
					from markdownx.models import MarkdownxField
 | 
				
			||||||
from mptt.models import TreeForeignKey
 | 
					from mptt.models import TreeForeignKey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,6 +44,9 @@ from stock import models as stock_models
 | 
				
			|||||||
from users import models as UserModels
 | 
					from users import models as UserModels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logger = logging.getLogger('inventree')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_next_po_number():
 | 
					def get_next_po_number():
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Returns the next available PurchaseOrder reference 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'))
 | 
					    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)
 | 
					        total = Money(0, target_currency)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # gather name reference
 | 
					        # gather name reference
 | 
				
			||||||
        price_ref = 'sale_price' if isinstance(self, SalesOrder) else 'purchase_price'
 | 
					        price_ref_tag = '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))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # extra lines
 | 
					        # order items
 | 
				
			||||||
        total += sum(a.quantity * convert_money(a.price, target_currency) for a in self.extra_lines.all() if a.price)
 | 
					        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
 | 
					        # set decimal-places
 | 
				
			||||||
        total.decimal_places = 4
 | 
					        total.decimal_places = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return total
 | 
					        return total
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -181,7 +181,15 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-dollar-sign'></span></td>
 | 
					        <td><span class='fas fa-dollar-sign'></span></td>
 | 
				
			||||||
        <td>{% trans "Total cost" %}</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>
 | 
					    </tr>
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -188,7 +188,15 @@ src="{% static 'img/blank_image.png' %}"
 | 
				
			|||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
        <td><span class='fas fa-dollar-sign'></span></td>
 | 
					        <td><span class='fas fa-dollar-sign'></span></td>
 | 
				
			||||||
        <td>{% trans "Total cost" %}</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>
 | 
					    </tr>
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user