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:
136
InvenTree/order/tasks.py
Normal file
136
InvenTree/order/tasks.py
Normal 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)
|
@ -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)
|
||||
|
@ -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')
|
||||
|
Reference in New Issue
Block a user