Improve notification of 'low stock' parts:

- Traverse up the variant tree
- Enable subscription by "category"
This commit is contained in:
Oliver 2021-11-04 00:28:10 +11:00
parent 1c6eb41341
commit 476a1342c1
4 changed files with 48 additions and 17 deletions

View File

@ -20,7 +20,7 @@ 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 jinja2 import Template
@ -47,6 +47,7 @@ from InvenTree import validators
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
from InvenTree.fields import InvenTreeURLField
from InvenTree.helpers import decimal2string, normalize, decimal2money
import InvenTree.tasks
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
@ -56,6 +57,7 @@ from company.models import SupplierPart
from stock import models as StockModels
import common.models
import part.settings as part_settings
@ -2085,9 +2087,24 @@ class Part(MPTTModel):
return len(self.get_related_parts())
def is_part_low_on_stock(self):
"""
Returns True if the total stock for this part is less than the minimum stock level
"""
return self.total_stock <= self.minimum_stock
@receiver(post_save, sender=Part, dispatch_uid='part_post_save_log')
def after_save_part(sender, instance: Part, **kwargs):
"""
Function to be executed after a Part is saved
"""
# Run this check in the background
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance)
def attach_file(instance, filename):
""" Function for storing a file for a PartAttachment

View File

@ -13,23 +13,28 @@ from common.models import InvenTree
import InvenTree.helpers
import InvenTree.tasks
from part.models import Part
import part.models
logger = logging.getLogger("inventree")
def notify_low_stock(part: Part):
def notify_low_stock(part: part.models.Part):
"""
Notify users who have starred a part when its stock quantity falls below the minimum threshold
"""
logger.info(f"Sending low stock notification email for {part.full_name}")
starred_users_email = EmailAddress.objects.filter(user__starred_parts__part=part)
# Get a list of users who are subcribed to this part
subscribers = part.get_subscribers()
emails = EmailAddress.objects.filter(
user__in=subscribers,
)
# TODO: In the future, include the part image in the email template
if len(starred_users_email) > 0:
if len(emails) > 0:
logger.info(f"Notify users regarding low stock of {part.name}")
context = {
# Pass the "Part" object through to the template context
@ -39,20 +44,24 @@ def notify_low_stock(part: Part):
subject = _(f'[InvenTree] {part.name} is low on stock')
html_message = render_to_string('email/low_stock_notification.html', context)
recipients = starred_users_email.values_list('email', flat=True)
recipients = emails.values_list('email', flat=True)
InvenTree.tasks.send_email(subject, '', recipients, html_message=html_message)
def notify_low_stock_if_required(part: Part):
def notify_low_stock_if_required(part: part.models.Part):
"""
Check if the stock quantity has fallen below the minimum threshold of part.
If true, notify the users who have subscribed to the part
"""
if part.is_part_low_on_stock():
InvenTree.tasks.offload_task(
'part.tasks.notify_low_stock',
part
)
# Run "up" the tree, to allow notification for "parent" parts
parts = part.get_ancestors(include_self=True, ascending=True)
for p in parts:
if p.is_part_low_on_stock():
InvenTree.tasks.offload_task(
'part.tasks.notify_low_stock',
p
)

View File

@ -27,7 +27,9 @@ from mptt.managers import TreeManager
from decimal import Decimal, InvalidOperation
from datetime import datetime, timedelta
from InvenTree import helpers
import InvenTree.tasks
import common.models
import report.models
@ -41,7 +43,6 @@ from users.models import Owner
from company import models as CompanyModels
from part import models as PartModels
from part import tasks as part_tasks
class StockLocation(InvenTreeTree):
@ -1658,16 +1659,18 @@ def after_delete_stock_item(sender, instance: StockItem, **kwargs):
Function to be executed after a StockItem object is deleted
"""
part_tasks.notify_low_stock_if_required(instance.part)
# Run this check in the background
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part)
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
def after_save_stock_item(sender, instance: StockItem, **kwargs):
"""
Hook function to be executed after StockItem object is saved/updated
Hook function to be executed after StockItem object is saved/updated
"""
part_tasks.notify_low_stock_if_required(instance.part)
# Run this check in the background
InvenTree.tasks.offload_task('part.tasks.notify_low_stock_if_required', instance.part)
class StockItemAttachment(InvenTreeAttachment):

View File

@ -17,13 +17,15 @@
{% block body %}
<tr style="height: 3rem; border-bottom: 1px solid">
<th>{% trans "Part Name" %}</th>
<th>{% trans "Available Quantity" %}</th>
<th>{% trans "Total Stock" %}</th>
<th>{% trans "Available" %}</th>
<th>{% trans "Minimum Quantity" %}</th>
</tr>
<tr style="height: 3rem">
<td style="text-align: center;">{{ part.full_name }}</td>
<td style="text-align: center;">{{ part.total_stock }}</td>
<td style="text-align: center;">{{ part.available_stock }}</td>
<td style="text-align: center;">{{ part.minimum_stock }}</td>
</tr>
{% endblock %}