From 6db6a70fc29840d688d1c8ed87866f613644df35 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 4 Nov 2021 13:32:14 +1100 Subject: [PATCH] Add task to check required stock for build order --- InvenTree/build/tasks.py | 85 +++++++++++++++++++ InvenTree/part/models.py | 17 ++-- InvenTree/part/tasks.py | 2 +- .../email/build_order_required_stock.html | 37 ++++++++ 4 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 InvenTree/build/tasks.py create mode 100644 InvenTree/templates/email/build_order_required_stock.html diff --git a/InvenTree/build/tasks.py b/InvenTree/build/tasks.py new file mode 100644 index 0000000000..a087b66129 --- /dev/null +++ b/InvenTree/build/tasks.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import logging + +from django.utils.translation import ugettext_lazy as _ +from django.template.loader import render_to_string + +from allauth.account.models import EmailAddress + +from common.models import NotificationEntry + +import build.models +import InvenTree.helpers +import InvenTree.tasks + + +logger = logging.getLogger('inventree') + + +def check_build_stock(build: build.models.Build): + """ + Check the required stock for a newly created build order, + and send an email out to any subscribed users if stock is low. + """ + + # Iterate through each of the parts required for this build + + lines = [] + + for bom_item in build.part.get_bom_items(): + + sub_part = bom_item.sub_part + + # The 'in stock' quantity depends on whether the bom_item allows variants + in_stock = sub_part.get_stock_count(include_variants=bom_item.allow_variants) + + allocated = sub_part.allocation_count() + + available = max(0, in_stock - allocated) + + required = bom_item.quantity * build.quantity + + if available < required: + # There is not sufficient stock for this part + + lines.append({ + 'link': InvenTree.helpers.construct_absolute_url(sub_part.get_absolute_url()), + 'part': sub_part, + 'in_stock': in_stock, + 'allocated': allocated, + 'available': available, + 'required': required, + }) + + if len(lines) == 0: + # Nothing to do + return + + # Are there any users subscribed to these parts? + subscribers = build.part.get_subscribers() + + emails = EmailAddress.objects.filter( + user__in=subscribers, + ) + + if len(emails) > 0: + + logger.info(f"Notifying users of stock required for build {build.pk}") + + context = { + 'link': InvenTree.helpers.construct_absolute_url(build.get_absolute_url()), + 'build': build, + 'part': build.part, + 'lines': lines, + } + + # Render the HTML message + html_message = render_to_string('email/build_order_required_stock.html', context) + + subject = "[InvenTree] " + _("Stock required for build order") + + recipients = emails.values_list('email', flat=True) + + InvenTree.tasks.send_email(subject, '', recipients, html_message=html_message) diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 1c50bc321e..7a15657a90 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1324,6 +1324,17 @@ class Part(MPTTModel): return query + def get_stock_count(self, include_variants=True): + """ + Return the total "in stock" count for this part + """ + + entries = self.stock_entries(in_stock=True, include_variants=include_variants) + + query = entries.aggregate(t=Coalesce(Sum('quantity'), Decimal(0))) + + return query['t'] + @property def total_stock(self): """ Return the total stock quantity for this part. @@ -1332,11 +1343,7 @@ class Part(MPTTModel): - If this part is a "template" (variants exist) then these are counted too """ - entries = self.stock_entries(in_stock=True) - - query = entries.aggregate(t=Coalesce(Sum('quantity'), Decimal(0))) - - return query['t'] + return self.get_stock_count() def get_bom_item_filter(self, include_inherited=True): """ diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index f4f1459214..0cd9cf09a7 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -50,7 +50,7 @@ def notify_low_stock(part: part.models.Part): 'link': InvenTree.helpers.construct_absolute_url(part.get_absolute_url()), } - subject = _(f'[InvenTree] {part.name} is low on stock') + subject = "[InvenTree] " + _("Low stock notification") html_message = render_to_string('email/low_stock_notification.html', context) recipients = emails.values_list('email', flat=True) diff --git a/InvenTree/templates/email/build_order_required_stock.html b/InvenTree/templates/email/build_order_required_stock.html new file mode 100644 index 0000000000..6b28d39f8e --- /dev/null +++ b/InvenTree/templates/email/build_order_required_stock.html @@ -0,0 +1,37 @@ +{% extends "email/email.html" %} + +{% load i18n %} +{% load inventree_extras %} + +{% block title %} +{% trans "Stock is required for the following build order" %}
+{% blocktrans with build=build.reference part=part.full_name quantity=build.quantity %}Build order {{ build }} - building {{ quantity }} x {{ part }}{% endblocktrans %} +
+

{% trans "Click on the following link to view this build order" %}: {{ link }}

+{% endblock title %} + +{% block body %} +{% trans "The following parts are low on required stock" %} + + + {% trans "Part" %} + {% trans "Required Quantity" %} + {% trans "Available" %} + + +{% for line in lines %} + + {{ line.part.full_name }} + + {% decimal line.required %} {% if line.part.units %}{{ line.part.units }}{% endif %} + + {% decimal line.available %} {% if line.part.units %}{{ line.part.units }}{% endif %} + + +{% endfor %} + +{% endblock body %} + +{% block footer_prefix %} +

{% blocktrans with part=part.name %}You are receiving this email because you are subscribed to notifications for this part {% endblocktrans %}.

+{% endblock footer_prefix %}