2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 12:35:46 +00:00

Overdue order notification (#3114)

* Adds a background task to notify users when a PurchaseOrder becomes overdue

* Schedule the overdue purchaseorder check to occur daily

* Allow notifications to be sent to "Owner" instances

- Extract user information from the Owner instance

* add unit test to ensure notifications are sent for overdue purchase orders

* Adds notification for overdue sales orders

* Clean up notification display panel

- Simplify rendering
- Order "newest at top"
- Element alignment tweaks

* style fixes

* More style fixes

* Tweak notification padding

* Fix import order

* Adds task to notify user of overdue build orders

* Adds unit tests for build order notifications

* Refactor subject line for emails:

- Use the configured instance title as a prefix for the subject line

* Add email template for overdue build orders

* Fix unit tests to accommodate new default value

* Logic error fix
This commit is contained in:
Oliver
2022-06-06 19:12:29 +10:00
committed by GitHub
parent 7b4d0605b8
commit 1e6bdfbcab
17 changed files with 439 additions and 22 deletions

136
InvenTree/order/tasks.py Normal file
View File

@ -0,0 +1,136 @@
"""Background tasks for the 'order' app"""
from datetime import datetime, timedelta
from django.utils.translation import gettext_lazy as _
import common.notifications
import InvenTree.helpers
import InvenTree.tasks
import order.models
from InvenTree.status_codes import PurchaseOrderStatus, SalesOrderStatus
from plugin.events import trigger_event
def notify_overdue_purchase_order(po: order.models.PurchaseOrder):
"""Notify users that a PurchaseOrder has just become 'overdue'"""
targets = []
if po.created_by:
targets.append(po.created_by)
if po.responsible:
targets.append(po.responsible)
name = _('Overdue Purchase Order')
context = {
'order': po,
'name': name,
'message': _(f'Purchase order {po} is now overdue'),
'link': InvenTree.helpers.construct_absolute_url(
po.get_absolute_url(),
),
'template': {
'html': 'email/overdue_purchase_order.html',
'subject': name,
}
}
event_name = 'order.overdue_purchase_order'
# Send a notification to the appropriate users
common.notifications.trigger_notification(
po,
event_name,
targets=targets,
context=context,
)
# Register a matching event to the plugin system
trigger_event(
event_name,
purchase_order=po.pk,
)
def check_overdue_purchase_orders():
"""Check if any outstanding PurchaseOrders have just become overdue:
- This check is performed daily
- Look at the 'target_date' of any outstanding PurchaseOrder objects
- If the 'target_date' expired *yesterday* then the order is just out of date
"""
yesterday = datetime.now().date() - timedelta(days=1)
overdue_orders = order.models.PurchaseOrder.objects.filter(
target_date=yesterday,
status__in=PurchaseOrderStatus.OPEN
)
for po in overdue_orders:
notify_overdue_purchase_order(po)
def notify_overdue_sales_order(so: order.models.SalesOrder):
"""Notify appropriate users that a SalesOrder has just become 'overdue'"""
targets = []
if so.created_by:
targets.append(so.created_by)
if so.responsible:
targets.append(so.responsible)
name = _('Overdue Sales Order')
context = {
'order': so,
'name': name,
'message': _(f"Sales order {so} is now overdue"),
'link': InvenTree.helpers.construct_absolute_url(
so.get_absolute_url(),
),
'template': {
'html': 'email/overdue_sales_order.html',
'subject': name,
}
}
event_name = 'order.overdue_sales_order'
# Send a notification to the appropriate users
common.notifications.trigger_notification(
so,
event_name,
targets=targets,
context=context,
)
# Register a matching event to the plugin system
trigger_event(
event_name,
sales_order=so.pk,
)
def check_overdue_sales_orders():
"""Check if any outstanding SalesOrders have just become overdue
- This check is performed daily
- Look at the 'target_date' of any outstanding SalesOrder objects
- If the 'target_date' expired *yesterday* then the order is just out of date
"""
yesterday = datetime.now().date() - timedelta(days=1)
overdue_orders = order.models.SalesOrder.objects.filter(
target_date=yesterday,
status__in=SalesOrderStatus.OPEN
)
for po in overdue_orders:
notify_overdue_sales_order(po)

View File

@ -2,21 +2,29 @@
from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.test import TestCase
from common.models import InvenTreeSetting
import order.tasks
from common.models import InvenTreeSetting, NotificationMessage
from company.models import Company
from InvenTree import status_codes as status
from order.models import (SalesOrder, SalesOrderAllocation, SalesOrderLineItem,
SalesOrderShipment)
from part.models import Part
from stock.models import StockItem
from users.models import Owner
class SalesOrderTest(TestCase):
"""Run tests to ensure that the SalesOrder model is working correctly."""
fixtures = [
'users',
]
def setUp(self):
"""Initial setup for this set of unit tests"""
# Create a Company to ship the goods to
@ -235,3 +243,20 @@ class SalesOrderTest(TestCase):
# Shipment should have default reference of '1'
self.assertEqual('1', order_2.pending_shipments()[0].reference)
def test_overdue_notification(self):
"""Test overdue sales order notification"""
self.order.created_by = get_user_model().objects.get(pk=3)
self.order.responsible = Owner.create(obj=Group.objects.get(pk=2))
self.order.target_date = datetime.now().date() - timedelta(days=1)
self.order.save()
# Check for overdue sales orders
order.tasks.check_overdue_sales_orders()
messages = NotificationMessage.objects.filter(
category='order.overdue_sales_order',
)
self.assertEqual(len(messages), 2)

View File

@ -3,12 +3,17 @@
from datetime import datetime, timedelta
import django.core.exceptions as django_exceptions
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.test import TestCase
import common.models
import order.tasks
from company.models import SupplierPart
from InvenTree.status_codes import PurchaseOrderStatus
from part.models import Part
from stock.models import StockLocation
from users.models import Owner
from .models import PurchaseOrder, PurchaseOrderLineItem
@ -24,7 +29,8 @@ class OrderTest(TestCase):
'part',
'location',
'stock',
'order'
'order',
'users',
]
def test_basics(self):
@ -197,3 +203,37 @@ class OrderTest(TestCase):
order.receive_line_item(line, loc, line.quantity, user=None)
self.assertEqual(order.status, PurchaseOrderStatus.COMPLETE)
def test_overdue_notification(self):
"""Test overdue purchase order notification
Ensure that a notification is sent when a PurchaseOrder becomes overdue
"""
po = PurchaseOrder.objects.get(pk=1)
# Created by 'sam'
po.created_by = get_user_model().objects.get(pk=4)
# Responsible : 'Engineers' group
responsible = Owner.create(obj=Group.objects.get(pk=2))
po.responsible = responsible
# Target date = yesterday
po.target_date = datetime.now().date() - timedelta(days=1)
po.save()
# Check for overdue purchase orders
order.tasks.check_overdue_purchase_orders()
for user_id in [2, 3, 4]:
messages = common.models.NotificationMessage.objects.filter(
category='order.overdue_purchase_order',
user__id=user_id,
)
self.assertTrue(messages.exists())
msg = messages.first()
self.assertEqual(msg.target_object_id, 1)
self.assertEqual(msg.name, 'Overdue Purchase Order')