From d8796f95356730784b31ddd078c618780e3c3918 Mon Sep 17 00:00:00 2001 From: rocheparadox Date: Fri, 29 Oct 2021 16:03:41 +0530 Subject: [PATCH] Notify users who have starred a part when that part's stock quantity falls below the minimum quanitity/threshold through email. --- InvenTree/InvenTree/tasks.py | 36 +++++++++++++++++-- InvenTree/stock/models.py | 14 +++++++- .../stock/low_stock_notification.html | 27 ++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 InvenTree/stock/templates/stock/low_stock_notification.html diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index aa17ef8603..9987a2593d 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -11,6 +11,7 @@ from django.utils import timezone from django.core.exceptions import AppRegistryNotReady from django.db.utils import OperationalError, ProgrammingError +from django.template.loader import render_to_string logger = logging.getLogger("inventree") @@ -52,7 +53,7 @@ def schedule_task(taskname, **kwargs): pass -def offload_task(taskname, force_sync=False, *args, **kwargs): +def offload_task(taskname, *args, force_sync=False, **kwargs): """ Create an AsyncTask if workers are running. This is different to a 'scheduled' task, @@ -290,7 +291,7 @@ def update_exchange_rates(): Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete() -def send_email(subject, body, recipients, from_email=None): +def send_email(subject, body, recipients, from_email=None, html_message=None): """ Send an email with the specified subject and body, to the specified recipients list. @@ -306,4 +307,35 @@ def send_email(subject, body, recipients, from_email=None): from_email, recipients, fail_silently=False, + html_message=html_message ) + + +def notify_low_stock(stock_item): + """ + Notify users who have starred a part when its stock quantity falls below the minimum threshold + """ + + from allauth.account.models import EmailAddress + starred_users = EmailAddress.objects.filter(user__starred_parts__part=stock_item.part) + + if len(starred_users) > 0: + logger.info(f"Notify users regarding low stock of {stock_item.part.name}") + body = f'Hi, {stock_item.part.name} is low on stock. Kindly do the needful.' + context = { + 'part_name': stock_item.part.name, + # Part url can be used to open the page of part in application from the email. + # It can be facilitated when the application base url is accessible programmatically. + # 'part_url': f'{application_base_url}/part/{stock_item.part.id}', + + 'message': body, + + # quantity is in decimal field datatype. Since the same datatype is used in models, + # it is not converted to number/integer, + 'part_quantity': stock_item.quantity, + 'minimum_quantity': stock_item.part.minimum_stock + } + subject = f'Attention! {stock_item.part.name} is low on stock' + html_message = render_to_string('stock/low_stock_notification.html', context) + recipients = starred_users.values_list('email', flat=True) + send_email(subject, body, recipients, html_message=html_message) diff --git a/InvenTree/stock/models.py b/InvenTree/stock/models.py index 1372e63406..ff8b91b105 100644 --- a/InvenTree/stock/models.py +++ b/InvenTree/stock/models.py @@ -17,7 +17,7 @@ from django.db.models import Sum, Q from django.db.models.functions import Coalesce from django.core.validators import MinValueValidator from django.contrib.auth.models import User -from django.db.models.signals import pre_delete +from django.db.models.signals import pre_delete, post_save from django.dispatch import receiver from markdownx.models import MarkdownxField @@ -36,6 +36,7 @@ import label.models from InvenTree.status_codes import StockStatus, StockHistoryCode from InvenTree.models import InvenTreeTree, InvenTreeAttachment from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField +from InvenTree import tasks as inventree_tasks from users.models import Owner @@ -1651,6 +1652,17 @@ def before_delete_stock_item(sender, instance, using, **kwargs): child.save() +@receiver(post_save, sender=StockItem) +def after_save_stock_item(sender, instance: StockItem, **kwargs): + """ + Check if the stock quantity has fallen below the minimum threshold of part. If yes, notify the users who have + starred the part + """ + + if instance.quantity <= instance.part.minimum_stock: + inventree_tasks.notify_low_stock(instance) + + class StockItemAttachment(InvenTreeAttachment): """ Model for storing file attachments against a StockItem object. diff --git a/InvenTree/stock/templates/stock/low_stock_notification.html b/InvenTree/stock/templates/stock/low_stock_notification.html new file mode 100644 index 0000000000..fa3799f6dd --- /dev/null +++ b/InvenTree/stock/templates/stock/low_stock_notification.html @@ -0,0 +1,27 @@ +

{{ message }}

+ + + + + + + + + + + + + + + + + + + + + + + +
Part low on stock
Part NameAvailable QuantityMinimum Quantity
{{ part_name }}{{ part_quantity }}{{ minimum_quantity }}
You are receiving this mail because you have starred the part {{ part_name }} in + Inventree application
+