diff --git a/src/backend/InvenTree/InvenTree/helpers_model.py b/src/backend/InvenTree/InvenTree/helpers_model.py index 7fe10777b3..d5ce76fe41 100644 --- a/src/backend/InvenTree/InvenTree/helpers_model.py +++ b/src/backend/InvenTree/InvenTree/helpers_model.py @@ -345,6 +345,7 @@ def notify_responsible( instance, sender, content: NotificationBody = InvenTreeNotificationBodies.NewOrder, + html_context: Optional[dict] = None, exclude=None, extra_users: Optional[list] = None, ): @@ -357,6 +358,7 @@ def notify_responsible( instance: The newly created instance sender: Sender model reference content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder. + html_context: Optional HTML context to include in the notification. Defaults to None. exclude (User, optional): User instance that should be excluded. Defaults to None. extra_users (list, optional): List of extra users to notify. Defaults to None. """ @@ -370,7 +372,14 @@ def notify_responsible( if extra_users: users.extend(extra_users) - notify_users(users, instance, sender, content=content, exclude=exclude) + notify_users( + users, + instance, + sender, + content=content, + html_context=html_context, + exclude=exclude, + ) def notify_users( @@ -378,6 +387,7 @@ def notify_users( instance, sender, content: NotificationBody = InvenTreeNotificationBodies.NewOrder, + html_context: Optional[dict] = None, exclude=None, ): """Notify all passed users or groups. @@ -390,6 +400,7 @@ def notify_users( instance: The newly created instance sender: Sender model reference content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder. + html_context (dict, optional): Optional HTML context to include in the notification. Defaults to None. exclude (User, optional): User instance that should be excluded. Defaults to None. """ # Setup context for notification parsing @@ -401,7 +412,9 @@ def notify_users( } # Setup notification context + # Extra HTML context is included, but cannot override the standard keys context = { + **(html_context or {}), 'instance': instance, 'name': content.name.format(**content_context), 'message': content.message.format(**content_context), @@ -409,8 +422,7 @@ def notify_users( 'template': {'subject': content.name.format(**content_context)}, } - tmp = content.template - if tmp: + if tmp := content.template: context['template']['html'] = tmp.format(**content_context) # Create notification diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index f275582da9..adbc50115d 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -1322,6 +1322,7 @@ class PurchaseOrder(TotalPriceMixin, Order): PurchaseOrder, exclude=user, content=InvenTreeNotificationBodies.ItemsReceived, + html_context={'order': self, 'stock_items': stock_items}, extra_users=notify_users, ) diff --git a/src/backend/InvenTree/order/tests.py b/src/backend/InvenTree/order/tests.py index bd4adecb93..203c1eef51 100644 --- a/src/backend/InvenTree/order/tests.py +++ b/src/backend/InvenTree/order/tests.py @@ -6,6 +6,7 @@ from decimal import Decimal import django.core.exceptions as django_exceptions from django.contrib.auth import get_user_model from django.contrib.auth.models import Group +from django.core import mail from django.test import TestCase from djmoney.money import Money @@ -22,6 +23,7 @@ from InvenTree.unit_test import ( ) from order.status_codes import PurchaseOrderStatus from part.models import Part +from plugin import registry from stock.models import StockItem, StockLocation from users.models import Owner @@ -556,6 +558,68 @@ class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase): # However *no* notification should have been generated for the creating user self.assertFalse(messages.filter(user__pk=3).exists()) + def test_received_email_notification(self): + """Test the email notification sent when items are received against a PurchaseOrder. + + - The responsible user should receive an email + - The email should list the received stock items + """ + user = get_user_model().objects.get(pk=4) + user.email = 'sam@demo.inventree.org' + user.save() + + # Enable email notifications for the responsible user + plugin = registry.get_plugin('inventree-email-notification') + self.assertIsNotNone(plugin) + plugin.set_user_setting('NOTIFY_BY_EMAIL', True, user) + + po = PurchaseOrder.objects.get(pk=1) + po.responsible = Owner.create(obj=user) + po.save() + po.place_order() + + # Ignore any emails sent when the order was placed + common.models.NotificationEntry.objects.all().delete() + mail.outbox.clear() + + location = StockLocation.objects.get(pk=1) + line_1 = PurchaseOrderLineItem.objects.get(pk=1) + line_2 = PurchaseOrderLineItem.objects.get(pk=3) + + stock_items = po.receive_line_items( + location, + [ + {'line_item': line_1, 'quantity': 15}, + {'line_item': line_2, 'quantity': 37}, + ], + user=get_user_model().objects.get(pk=1), + ) + + self.assertEqual(stock_items.count(), 2) + + # Exactly one email should have been sent, to the responsible user + self.assertEqual(len(mail.outbox), 1) + email = mail.outbox[0] + + self.assertEqual(email.to, [user.email]) + self.assertIn('Items Received', email.subject) + + html = email.alternatives[0][0] + + # Order information + self.assertIn('Items have been received against a purchase order', html) + self.assertIn(po.get_absolute_url(), html) + + # Table of received items + self.assertIn('
{% trans "Click on the following link to view this order" %}: {{ link }}
{% endif %} {% endblock title %} + +{% block body %} +