mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-02 13:28:49 +00:00
Notify users who have starred a part when that part's stock quantity falls below the minimum quanitity/threshold through email.
This commit is contained in:
parent
5cd9be6845
commit
d8796f9535
@ -11,6 +11,7 @@ from django.utils import timezone
|
|||||||
|
|
||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("inventree")
|
logger = logging.getLogger("inventree")
|
||||||
@ -52,7 +53,7 @@ def schedule_task(taskname, **kwargs):
|
|||||||
pass
|
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.
|
Create an AsyncTask if workers are running.
|
||||||
This is different to a 'scheduled' task,
|
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()
|
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,
|
Send an email with the specified subject and body,
|
||||||
to the specified recipients list.
|
to the specified recipients list.
|
||||||
@ -306,4 +307,35 @@ def send_email(subject, body, recipients, from_email=None):
|
|||||||
from_email,
|
from_email,
|
||||||
recipients,
|
recipients,
|
||||||
fail_silently=False,
|
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)
|
||||||
|
@ -17,7 +17,7 @@ from django.db.models import Sum, Q
|
|||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
from django.contrib.auth.models import User
|
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 django.dispatch import receiver
|
||||||
|
|
||||||
from markdownx.models import MarkdownxField
|
from markdownx.models import MarkdownxField
|
||||||
@ -36,6 +36,7 @@ import label.models
|
|||||||
from InvenTree.status_codes import StockStatus, StockHistoryCode
|
from InvenTree.status_codes import StockStatus, StockHistoryCode
|
||||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
||||||
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
|
from InvenTree.fields import InvenTreeModelMoneyField, InvenTreeURLField
|
||||||
|
from InvenTree import tasks as inventree_tasks
|
||||||
|
|
||||||
from users.models import Owner
|
from users.models import Owner
|
||||||
|
|
||||||
@ -1651,6 +1652,17 @@ def before_delete_stock_item(sender, instance, using, **kwargs):
|
|||||||
child.save()
|
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):
|
class StockItemAttachment(InvenTreeAttachment):
|
||||||
"""
|
"""
|
||||||
Model for storing file attachments against a StockItem object.
|
Model for storing file attachments against a StockItem object.
|
||||||
|
27
InvenTree/stock/templates/stock/low_stock_notification.html
Normal file
27
InvenTree/stock/templates/stock/low_stock_notification.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<p>{{ message }}</p>
|
||||||
|
|
||||||
|
<table style="border-collapse:collapse; width: 80%;margin-left: 10%; font-size: 1rem">
|
||||||
|
|
||||||
|
<tr style="background: aliceblue; height: 4rem;">
|
||||||
|
<th colspan="3" style="padding-bottom: 1rem; font-size: 1.5rem; color:rgb(210,0, 0)">Part low on stock</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 3rem; border-bottom: 1px solid">
|
||||||
|
<th>Part Name</th>
|
||||||
|
<th>Available Quantity</th>
|
||||||
|
<th>Minimum Quantity</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 3rem">
|
||||||
|
<td style="text-align: center">{{ part_name }}</td>
|
||||||
|
<td style="text-align: center">{{ part_quantity }}</td>
|
||||||
|
<td style="text-align: center">{{ minimum_quantity }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="background-color: aliceblue;height: 4rem;">
|
||||||
|
<td colspan="3" style="padding-top:1rem; text-align: center">You are receiving this mail because you have starred the part {{ part_name }} in
|
||||||
|
Inventree application</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user