mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Improve notification of 'low stock' parts:
- Traverse up the variant tree - Enable subscription by "category"
This commit is contained in:
parent
1c6eb41341
commit
476a1342c1
@ -20,7 +20,7 @@ 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 jinja2 import Template
|
from jinja2 import Template
|
||||||
@ -47,6 +47,7 @@ from InvenTree import validators
|
|||||||
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
from InvenTree.models import InvenTreeTree, InvenTreeAttachment
|
||||||
from InvenTree.fields import InvenTreeURLField
|
from InvenTree.fields import InvenTreeURLField
|
||||||
from InvenTree.helpers import decimal2string, normalize, decimal2money
|
from InvenTree.helpers import decimal2string, normalize, decimal2money
|
||||||
|
import InvenTree.tasks
|
||||||
|
|
||||||
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
from InvenTree.status_codes import BuildStatus, PurchaseOrderStatus, SalesOrderStatus
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ from company.models import SupplierPart
|
|||||||
from stock import models as StockModels
|
from stock import models as StockModels
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
import part.settings as part_settings
|
import part.settings as part_settings
|
||||||
|
|
||||||
|
|
||||||
@ -2085,9 +2087,24 @@ class Part(MPTTModel):
|
|||||||
return len(self.get_related_parts())
|
return len(self.get_related_parts())
|
||||||
|
|
||||||
def is_part_low_on_stock(self):
|
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
|
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):
|
def attach_file(instance, filename):
|
||||||
""" Function for storing a file for a PartAttachment
|
""" Function for storing a file for a PartAttachment
|
||||||
|
|
||||||
|
@ -13,23 +13,28 @@ from common.models import InvenTree
|
|||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
import InvenTree.tasks
|
import InvenTree.tasks
|
||||||
|
|
||||||
from part.models import Part
|
import part.models
|
||||||
|
|
||||||
logger = logging.getLogger("inventree")
|
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
|
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}")
|
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
|
# 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}")
|
logger.info(f"Notify users regarding low stock of {part.name}")
|
||||||
context = {
|
context = {
|
||||||
# Pass the "Part" object through to the template 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')
|
subject = _(f'[InvenTree] {part.name} is low on stock')
|
||||||
html_message = render_to_string('email/low_stock_notification.html', context)
|
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)
|
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.
|
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 true, notify the users who have subscribed to the part
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if part.is_part_low_on_stock():
|
# Run "up" the tree, to allow notification for "parent" parts
|
||||||
InvenTree.tasks.offload_task(
|
parts = part.get_ancestors(include_self=True, ascending=True)
|
||||||
'part.tasks.notify_low_stock',
|
|
||||||
part
|
for p in parts:
|
||||||
)
|
if p.is_part_low_on_stock():
|
||||||
|
InvenTree.tasks.offload_task(
|
||||||
|
'part.tasks.notify_low_stock',
|
||||||
|
p
|
||||||
|
)
|
||||||
|
@ -27,7 +27,9 @@ from mptt.managers import TreeManager
|
|||||||
|
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from InvenTree import helpers
|
from InvenTree import helpers
|
||||||
|
import InvenTree.tasks
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
import report.models
|
import report.models
|
||||||
@ -41,7 +43,6 @@ from users.models import Owner
|
|||||||
|
|
||||||
from company import models as CompanyModels
|
from company import models as CompanyModels
|
||||||
from part import models as PartModels
|
from part import models as PartModels
|
||||||
from part import tasks as part_tasks
|
|
||||||
|
|
||||||
|
|
||||||
class StockLocation(InvenTreeTree):
|
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
|
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')
|
@receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log')
|
||||||
def after_save_stock_item(sender, instance: StockItem, **kwargs):
|
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):
|
class StockItemAttachment(InvenTreeAttachment):
|
||||||
|
@ -17,13 +17,15 @@
|
|||||||
{% block body %}
|
{% block body %}
|
||||||
<tr style="height: 3rem; border-bottom: 1px solid">
|
<tr style="height: 3rem; border-bottom: 1px solid">
|
||||||
<th>{% trans "Part Name" %}</th>
|
<th>{% trans "Part Name" %}</th>
|
||||||
<th>{% trans "Available Quantity" %}</th>
|
<th>{% trans "Total Stock" %}</th>
|
||||||
|
<th>{% trans "Available" %}</th>
|
||||||
<th>{% trans "Minimum Quantity" %}</th>
|
<th>{% trans "Minimum Quantity" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr style="height: 3rem">
|
<tr style="height: 3rem">
|
||||||
<td style="text-align: center;">{{ part.full_name }}</td>
|
<td style="text-align: center;">{{ part.full_name }}</td>
|
||||||
<td style="text-align: center;">{{ part.total_stock }}</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>
|
<td style="text-align: center;">{{ part.minimum_stock }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
Loading…
Reference in New Issue
Block a user