mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Merge branch 'master' of https://github.com/inventree/InvenTree into plugin-2037
This commit is contained in:
		| @@ -69,6 +69,35 @@ def getStaticUrl(filename): | ||||
|     return os.path.join(STATIC_URL, str(filename)) | ||||
|  | ||||
|  | ||||
| def construct_absolute_url(*arg): | ||||
|     """ | ||||
|     Construct (or attempt to construct) an absolute URL from a relative URL. | ||||
|  | ||||
|     This is useful when (for example) sending an email to a user with a link | ||||
|     to something in the InvenTree web framework. | ||||
|  | ||||
|     This requires the BASE_URL configuration option to be set! | ||||
|     """ | ||||
|  | ||||
|     base = str(InvenTreeSetting.get_setting('INVENTREE_BASE_URL')) | ||||
|  | ||||
|     url = '/'.join(arg) | ||||
|  | ||||
|     if not base: | ||||
|         return url | ||||
|  | ||||
|     # Strip trailing slash from base url | ||||
|     if base.endswith('/'): | ||||
|         base = base[:-1] | ||||
|  | ||||
|     if url.startswith('/'): | ||||
|         url = url[1:] | ||||
|  | ||||
|     url = f"{base}/{url}" | ||||
|  | ||||
|     return url | ||||
|  | ||||
|  | ||||
| def getBlankImage(): | ||||
|     """ | ||||
|     Return the qualified path for the 'blank image' placeholder. | ||||
|   | ||||
| @@ -141,6 +141,10 @@ | ||||
|     float: left; | ||||
| } | ||||
|  | ||||
| .navbar-spacer { | ||||
|     height: 60px; | ||||
| } | ||||
|  | ||||
| #navbar-barcode-li { | ||||
|     border-left: none; | ||||
|     border-right: none; | ||||
| @@ -545,7 +549,6 @@ | ||||
| .inventree-body { | ||||
|     width: 100%; | ||||
|     padding: 5px; | ||||
|     margin-top: 10px; | ||||
| } | ||||
|  | ||||
| .inventree-pre-content { | ||||
| @@ -562,10 +565,6 @@ | ||||
|     transition: 0.1s; | ||||
| } | ||||
|  | ||||
| .body { | ||||
|     padding-top: 50px; | ||||
| } | ||||
|  | ||||
| .modal { | ||||
|   overflow: hidden; | ||||
|   z-index: 9999; | ||||
|   | ||||
| @@ -52,7 +52,7 @@ def schedule_task(taskname, **kwargs): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| def offload_task(taskname, force_sync=False, *args, **kwargs): | ||||
| def offload_task(taskname, *args, force_sync=False, **kwargs): | ||||
|     """ | ||||
|         Create an AsyncTask if workers are running. | ||||
|         This is different to a 'scheduled' task, | ||||
| @@ -108,7 +108,7 @@ def offload_task(taskname, force_sync=False, *args, **kwargs): | ||||
|             return | ||||
|          | ||||
|         # Workers are not running: run it as synchronous task | ||||
|         _func() | ||||
|         _func(*args, **kwargs) | ||||
|  | ||||
|  | ||||
| def heartbeat(): | ||||
| @@ -290,7 +290,7 @@ def update_exchange_rates(): | ||||
|     Rate.objects.filter(backend="InvenTreeExchange").exclude(currency__in=currency_codes()).delete() | ||||
|  | ||||
|  | ||||
| def send_email(subject, body, recipients, from_email=None): | ||||
| def send_email(subject, body, recipients, from_email=None, html_message=None): | ||||
|     """ | ||||
|     Send an email with the specified subject and body, | ||||
|     to the specified recipients list. | ||||
| @@ -306,4 +306,5 @@ def send_email(subject, body, recipients, from_email=None): | ||||
|         from_email, | ||||
|         recipients, | ||||
|         fail_silently=False, | ||||
|         html_message=html_message | ||||
|     ) | ||||
|   | ||||
| @@ -102,9 +102,9 @@ class APITests(InvenTreeAPITestCase): | ||||
|  | ||||
|     fixtures = [ | ||||
|         'location', | ||||
|         'stock', | ||||
|         'part', | ||||
|         'category', | ||||
|         'part', | ||||
|         'stock' | ||||
|     ] | ||||
|  | ||||
|     token = None | ||||
|   | ||||
| @@ -142,7 +142,7 @@ | ||||
|                         <td><span class='fas fa-calendar-alt'></span></td> | ||||
|                         <td>{% trans "Completed" %}</td> | ||||
|                         {% if build.completion_date %} | ||||
|                         <td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td> | ||||
|                         <td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge badge-right rounded-pill bg-dark'>{{ build.completed_by }}</span>{% endif %}</td> | ||||
|                         {% else %} | ||||
|                         <td><em>{% trans "Build not complete" %}</em></td> | ||||
|                         {% endif %} | ||||
|   | ||||
| @@ -830,19 +830,19 @@ class InvenTreeSetting(BaseInvenTreeSetting): | ||||
|         # login / SSO | ||||
|         'LOGIN_ENABLE_PWD_FORGOT': { | ||||
|             'name': _('Enable password forgot'), | ||||
|             'description': _('Enable password forgot function on the login-pages'), | ||||
|             'description': _('Enable password forgot function on the login pages'), | ||||
|             'default': True, | ||||
|             'validator': bool, | ||||
|         }, | ||||
|         'LOGIN_ENABLE_REG': { | ||||
|             'name': _('Enable registration'), | ||||
|             'description': _('Enable self-registration for users on the login-pages'), | ||||
|             'description': _('Enable self-registration for users on the login pages'), | ||||
|             'default': False, | ||||
|             'validator': bool, | ||||
|         }, | ||||
|         'LOGIN_ENABLE_SSO': { | ||||
|             'name': _('Enable SSO'), | ||||
|             'description': _('Enable SSO on the login-pages'), | ||||
|             'description': _('Enable SSO on the login pages'), | ||||
|             'default': False, | ||||
|             'validator': bool, | ||||
|         }, | ||||
|   | ||||
| @@ -123,7 +123,7 @@ src="{% static 'img/blank_image.png' %}" | ||||
|     <tr> | ||||
|         <td><span class='fas fa-calendar-alt'></span></td> | ||||
|         <td>{% trans "Created" %}</td> | ||||
|         <td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td> | ||||
|         <td>{{ order.creation_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.created_by }}</span></td> | ||||
|     </tr> | ||||
|     {% if order.issue_date %} | ||||
|     <tr> | ||||
| @@ -143,7 +143,7 @@ src="{% static 'img/blank_image.png' %}" | ||||
|     <tr> | ||||
|         <td><span class='fas fa-calendar-alt'></span></td> | ||||
|         <td>{% trans "Received" %}</td> | ||||
|         <td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td> | ||||
|         <td>{{ order.complete_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.received_by }}</span></td> | ||||
|     </tr> | ||||
|     {% endif %} | ||||
|     {% if order.responsible %} | ||||
|   | ||||
| @@ -128,7 +128,7 @@ src="{% static 'img/blank_image.png' %}" | ||||
|     <tr> | ||||
|         <td><span class='fas fa-calendar-alt'></span></td> | ||||
|         <td>{% trans "Created" %}</td> | ||||
|         <td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td> | ||||
|         <td>{{ order.creation_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.created_by }}</span></td> | ||||
|     </tr> | ||||
|     {% if order.target_date %} | ||||
|     <tr> | ||||
| @@ -141,14 +141,14 @@ src="{% static 'img/blank_image.png' %}" | ||||
|     <tr> | ||||
|         <td><span class='fas fa-truck'></span></td> | ||||
|         <td>{% trans "Shipped" %}</td> | ||||
|         <td>{{ order.shipment_date }}<span class='badge'>{{ order.shipped_by }}</span></td> | ||||
|         <td>{{ order.shipment_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.shipped_by }}</span></td> | ||||
|     </tr> | ||||
|     {% endif %} | ||||
|     {% if order.status == PurchaseOrderStatus.COMPLETE %} | ||||
|     <tr> | ||||
|         <td><span class='fas fa-calendar-alt'></span></td> | ||||
|         <td>{% trans "Received" %}</td> | ||||
|         <td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td> | ||||
|         <td>{{ order.complete_date }}<span class='badge badge-right rounded-pill bg-dark'>{{ order.received_by }}</span></td> | ||||
|     </tr> | ||||
|     {% endif %} | ||||
|     {% if order.responsible %} | ||||
|   | ||||
| @@ -1988,6 +1988,9 @@ class Part(MPTTModel): | ||||
|     def related_count(self): | ||||
|         return len(self.get_related_parts()) | ||||
|  | ||||
|     def is_part_low_on_stock(self): | ||||
|         return self.total_stock <= self.minimum_stock | ||||
|  | ||||
|  | ||||
| def attach_file(instance, filename): | ||||
|     """ Function for storing a file for a PartAttachment | ||||
|   | ||||
							
								
								
									
										58
									
								
								InvenTree/part/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								InvenTree/part/tasks.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| # -*- 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 InvenTree | ||||
|  | ||||
| import InvenTree.helpers | ||||
| import InvenTree.tasks | ||||
|  | ||||
| from part.models import Part | ||||
|  | ||||
| logger = logging.getLogger("inventree") | ||||
|  | ||||
|  | ||||
| def notify_low_stock(part: Part): | ||||
|     """ | ||||
|     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}") | ||||
|  | ||||
|     starred_users_email = EmailAddress.objects.filter(user__starred_parts__part=part) | ||||
|  | ||||
|     # TODO: In the future, include the part image in the email template | ||||
|  | ||||
|     if len(starred_users_email) > 0: | ||||
|         logger.info(f"Notify users regarding low stock of {part.name}") | ||||
|         context = { | ||||
|             # Pass the "Part" object through to the template context | ||||
|             'part': part, | ||||
|             'link': InvenTree.helpers.construct_absolute_url(part.get_absolute_url()), | ||||
|         } | ||||
|  | ||||
|         subject = _(f'[InvenTree] {part.name} is low on stock') | ||||
|         html_message = render_to_string('email/low_stock_notification.html', context) | ||||
|         recipients = starred_users_email.values_list('email', flat=True) | ||||
|  | ||||
|         InvenTree.tasks.send_email(subject, '', recipients, html_message=html_message) | ||||
|  | ||||
|  | ||||
| def notify_low_stock_if_required(part: 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 part.is_part_low_on_stock(): | ||||
|         InvenTree.tasks.offload_task( | ||||
|             'part.tasks.notify_low_stock', | ||||
|             part | ||||
|         ) | ||||
| @@ -8,58 +8,55 @@ | ||||
| {% include "sidebar_link.html" with url=url text="Return to BOM" icon="fa-undo" %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block page_content %} | ||||
| {% block heading %} | ||||
| {% trans "Upload Bill of Materials" %} | ||||
| {% endblock %} | ||||
|  | ||||
| <div class='panel' id='panel-upload-file'> | ||||
|     <div class='panel-heading'> | ||||
|         {% block heading %} | ||||
|         <h4>{% trans "Upload Bill of Materials" %}</h4> | ||||
|         {{ wizard.form.media }} | ||||
|         {% endblock %} | ||||
| {% block actions %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block page_info %} | ||||
| <div class='panel-content'> | ||||
|     <p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %} | ||||
|     {% if description %}- {{ description }}{% endif %}</p> | ||||
|  | ||||
|     <form action="" method="post" class='js-modal-form' enctype="multipart/form-data"> | ||||
|     {% csrf_token %} | ||||
|     {% load crispy_forms_tags %} | ||||
|  | ||||
|     {% block form_buttons_top %} | ||||
|     {% endblock form_buttons_top %} | ||||
|  | ||||
|     {% block form_alert %} | ||||
|     <div class='alert alert-info alert-block'> | ||||
|         <strong>{% trans "Requirements for BOM upload" %}:</strong> | ||||
|         <ul> | ||||
|             <li>{% trans "The BOM file must contain the required named columns as provided in the " %} <strong><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></strong></li> | ||||
|             <li>{% trans "Each part must already exist in the database" %}</li> | ||||
|         </ul> | ||||
|     </div> | ||||
|     <div class='panel-content'> | ||||
|         {% block details %} | ||||
|     {% endblock %}  | ||||
|  | ||||
|         <p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %} | ||||
|         {% if description %}- {{ description }}{% endif %}</p> | ||||
|     <table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'> | ||||
|     {{ wizard.management_form }} | ||||
|     {% block form_content %} | ||||
|     {% crispy wizard.form %} | ||||
|     {% endblock form_content %} | ||||
|     </table> | ||||
|  | ||||
|         <form action="" method="post" class='js-modal-form' enctype="multipart/form-data"> | ||||
|         {% csrf_token %} | ||||
|         {% load crispy_forms_tags %} | ||||
|  | ||||
|         {% block form_buttons_top %} | ||||
|         {% endblock form_buttons_top %} | ||||
|  | ||||
|         {% block form_alert %} | ||||
|         <div class='alert alert-info alert-block'> | ||||
|             <strong>{% trans "Requirements for BOM upload" %}:</strong> | ||||
|             <ul> | ||||
|                 <li>{% trans "The BOM file must contain the required named columns as provided in the " %} <strong><a href="/part/bom_template/">{% trans "BOM Upload Template" %}</a></strong></li> | ||||
|                 <li>{% trans "Each part must already exist in the database" %}</li> | ||||
|             </ul> | ||||
|         </div> | ||||
|         {% endblock %}  | ||||
|  | ||||
|         <table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'> | ||||
|         {{ wizard.management_form }} | ||||
|         {% block form_content %} | ||||
|         {% crispy wizard.form %} | ||||
|         {% endblock form_content %} | ||||
|         </table> | ||||
|  | ||||
|         {% block form_buttons_bottom %} | ||||
|         {% if wizard.steps.prev %} | ||||
|         <button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-outline-secondary">{% trans "Previous Step" %}</button> | ||||
|         {% endif %} | ||||
|         <button type="submit" class="save btn btn-outline-secondary">{% trans "Upload File" %}</button> | ||||
|         </form> | ||||
|         {% endblock form_buttons_bottom %} | ||||
|  | ||||
|         {% endblock details %} | ||||
|     </div> | ||||
|  | ||||
| {% endblock page_content %} | ||||
|     {% block form_buttons_bottom %} | ||||
|     {% if wizard.steps.prev %} | ||||
|     <button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}" class="save btn btn-outline-secondary">{% trans "Previous Step" %}</button> | ||||
|     {% endif %} | ||||
|     <button type="submit" class="save btn btn-outline-secondary">{% trans "Upload File" %}</button> | ||||
|     </form> | ||||
|     {% endblock form_buttons_bottom %} | ||||
| </div> | ||||
| {% endblock page_info %} | ||||
|  | ||||
| {% block js_ready %} | ||||
| {{ block.super }} | ||||
|  | ||||
| enableSidebar('bom-upload'); | ||||
|  | ||||
| {% endblock js_ready %} | ||||
|   | ||||
| @@ -210,7 +210,8 @@ | ||||
|                 {% else %} | ||||
|                 parent: null, | ||||
|                 {% endif %} | ||||
|             } | ||||
|             }, | ||||
|             allowTreeView: true, | ||||
|         } | ||||
|     ); | ||||
|  | ||||
|   | ||||
| @@ -20,13 +20,6 @@ | ||||
|         <!-- Details Table --> | ||||
|         <table class="table table-striped table-condensed"> | ||||
|             <col width='25'> | ||||
|             {% if part.IPN %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-tag'></span></td> | ||||
|                 <td>{% trans "IPN" %}</td> | ||||
|                 <td>{{ part.IPN }}{% include "clip.html"%}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-shapes'></span></td> | ||||
|                 <td>{% trans "Name" %}</td> | ||||
| @@ -37,6 +30,22 @@ | ||||
|                 <td>{% trans "Description" %}</td> | ||||
|                 <td>{{ part.description }}{% include "clip.html"%}</td> | ||||
|             </tr> | ||||
|             {% if part.category %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-sitemap'></span></td> | ||||
|                 <td>{% trans "Category" %}</td> | ||||
|                 <td> | ||||
|                     <a href='{% url "category-detail" part.category.pk %}'>{{ part.category }}</a> | ||||
|                 </td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if part.IPN %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-tag'></span></td> | ||||
|                 <td>{% trans "IPN" %}</td> | ||||
|                 <td>{{ part.IPN }}{% include "clip.html"%}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if part.revision %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-code-branch'></span></td> | ||||
| @@ -44,6 +53,20 @@ | ||||
|                 <td>{{ part.revision }}{% include "clip.html"%}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if part.units %} | ||||
|             <tr> | ||||
|                 <td></td> | ||||
|                 <td>{% trans "Units" %}</td> | ||||
|                 <td>{{ part.units }}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if part.minimum_stock %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-less-than-equal'></span></td> | ||||
|                 <td>{% trans "Minimum stock level" %}</td> | ||||
|                 <td>{{ part.minimum_stock }}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if part.keywords %} | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-key'></span></td> | ||||
| @@ -64,7 +87,7 @@ | ||||
|                 <td> | ||||
|                     {{ part.creation_date }} | ||||
|                     {% if part.creation_user %} | ||||
|                     <span class='badge'>{{ part.creation_user }}</span> | ||||
|                     <span class='badge badge-right rounded-pill bg-dark'>{{ part.creation_user }}</span> | ||||
|                     {% endif %} | ||||
|                 </td> | ||||
|             </tr> | ||||
| @@ -79,7 +102,9 @@ | ||||
|             <tr> | ||||
|                 <td><span class='fas fa-search-location'></span></td> | ||||
|                 <td>{% trans "Default Location" %}</td> | ||||
|                 <td>{{ part.default_location }}</td> | ||||
|                 <td> | ||||
|                     <a href='{% url "stock-location-detail" part.default_location.pk %}'>{{ part.default_location }}</a> | ||||
|                 </td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if part.default_supplier %} | ||||
|   | ||||
| @@ -122,6 +122,12 @@ def inventree_title(*args, **kwargs): | ||||
|     return version.inventreeInstanceTitle() | ||||
|  | ||||
|  | ||||
| @register.simple_tag() | ||||
| def inventree_base_url(*args, **kwargs): | ||||
|     """ Return the INVENTREE_BASE_URL setting """ | ||||
|     return InvenTreeSetting.get_setting('INVENTREE_BASE_URL') | ||||
|  | ||||
|  | ||||
| @register.simple_tag() | ||||
| def python_version(*args, **kwargs): | ||||
|     """ | ||||
|   | ||||
| @@ -14,6 +14,8 @@ from stock.models import StockItem | ||||
|  | ||||
| from common.models import InvenTreeSetting | ||||
|  | ||||
| import InvenTree.helpers | ||||
|  | ||||
| register = template.Library() | ||||
|  | ||||
|  | ||||
| @@ -119,18 +121,10 @@ def internal_link(link, text): | ||||
|  | ||||
|     text = str(text) | ||||
|  | ||||
|     base_url = InvenTreeSetting.get_setting('INVENTREE_BASE_URL') | ||||
|     url = InvenTree.helpers.construct_absolute_url(link) | ||||
|  | ||||
|     # If the base URL is not set, just return the text | ||||
|     if not base_url: | ||||
|     if not url: | ||||
|         return text | ||||
|  | ||||
|     if not base_url.endswith('/'): | ||||
|         base_url += '/' | ||||
|  | ||||
|     if base_url.endswith('/') and link.startswith('/'): | ||||
|         link = link[1:] | ||||
|  | ||||
|     url = f"{base_url}{link}" | ||||
|  | ||||
|     return mark_safe(f'<a href="{url}">{text}</a>') | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from django.db.models import Sum, Q | ||||
| from django.db.models.functions import Coalesce | ||||
| from django.core.validators import MinValueValidator | ||||
| 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, post_delete | ||||
| from django.dispatch import receiver | ||||
|  | ||||
| from markdownx.models import MarkdownxField | ||||
| @@ -41,6 +41,7 @@ from users.models import Owner | ||||
|  | ||||
| from company import models as CompanyModels | ||||
| from part import models as PartModels | ||||
| from part import tasks as part_tasks | ||||
|  | ||||
|  | ||||
| class StockLocation(InvenTreeTree): | ||||
| @@ -1651,6 +1652,24 @@ def before_delete_stock_item(sender, instance, using, **kwargs): | ||||
|         child.save() | ||||
|  | ||||
|  | ||||
| @receiver(post_delete, sender=StockItem, dispatch_uid='stock_item_post_delete_log') | ||||
| def after_delete_stock_item(sender, instance: StockItem, **kwargs): | ||||
|     """ | ||||
|     Function to be executed after a StockItem object is deleted | ||||
|     """ | ||||
|  | ||||
|     part_tasks.notify_low_stock_if_required(instance.part) | ||||
|  | ||||
|  | ||||
| @receiver(post_save, sender=StockItem, dispatch_uid='stock_item_post_save_log') | ||||
| def after_save_stock_item(sender, instance: StockItem, **kwargs): | ||||
|     """ | ||||
|    Hook function to be executed after StockItem object is saved/updated | ||||
|     """ | ||||
|  | ||||
|     part_tasks.notify_low_stock_if_required(instance.part) | ||||
|  | ||||
|  | ||||
| class StockItemAttachment(InvenTreeAttachment): | ||||
|     """ | ||||
|     Model for storing file attachments against a StockItem object. | ||||
|   | ||||
| @@ -393,7 +393,7 @@ | ||||
|         <td><span class='fas fa-calendar-alt'></span></td> | ||||
|         <td>{% trans "Last Stocktake" %}</td> | ||||
|         {% if item.stocktake_date %} | ||||
|         <td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td> | ||||
|         <td>{{ item.stocktake_date }} <span class='badge badge-right rounded-pill bg-dark'>{{ item.stocktake_user }}</span></td> | ||||
|         {% else %} | ||||
|         <td><em>{% trans "No stocktake performed" %}</em></td> | ||||
|         {% endif %} | ||||
|   | ||||
| @@ -183,7 +183,8 @@ | ||||
|             {% else %} | ||||
|             parent: 'null', | ||||
|             {% endif %} | ||||
|         } | ||||
|         }, | ||||
|         allowTreeView: true, | ||||
|     }); | ||||
|      | ||||
|     linkButtonsToSelection( | ||||
|   | ||||
| @@ -36,7 +36,7 @@ If this location is deleted, these items will be moved to the top level 'Stock' | ||||
|  | ||||
| <ul class='list-group'> | ||||
|     {% for item in location.stock_items.all %} | ||||
|     <li class='list-group-item'><strong>{{ item.part.full_name }}</strong> - <em>{{ item.part.description }}</em><span class='badge'>{% decimal item.quantity %}</span></li> | ||||
|     <li class='list-group-item'><strong>{{ item.part.full_name }}</strong> - <em>{{ item.part.description }}</em><span class='badge badge-right rounded-pill bg-dark'>{% decimal item.quantity %}</span></li> | ||||
|     {% endfor %} | ||||
| </ul> | ||||
| {% endif %} | ||||
|   | ||||
| @@ -7,6 +7,8 @@ | ||||
| {% inventree_title %} | {% trans "Index" %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block breadcrumb_list %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block sidebar %} | ||||
| <!-- Sidebar data is filled dynamically for the index page--> | ||||
|   | ||||
| @@ -8,6 +8,9 @@ | ||||
| {% inventree_title %} | {% trans "Search Results" %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block breadcrumb_list %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <div class='panel panel-inventree'> | ||||
|   | ||||
| @@ -7,6 +7,12 @@ | ||||
| {% trans "Category Settings" %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block actions %} | ||||
| <button class='btn btn-success' id='new-cat-param' disabled=''> | ||||
|     <div class='fas fa-plus-circle'></div> {% trans "New Parameter" %} | ||||
| </button> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <div class='row'> | ||||
| @@ -21,12 +27,6 @@ | ||||
|     </form> | ||||
| </div> | ||||
|  | ||||
| <div id='cat-param-buttons'> | ||||
|     <button class='btn btn-success' id='new-cat-param' disabled=''> | ||||
|         <div class='fas fa-plus-circle'></div> {% trans "New Parameter" %} | ||||
|     </button> | ||||
| </div> | ||||
|  | ||||
| <table class='table table-striped table-condensed' id='cat-param-table' data-toolbar='#cat-param-buttons'> | ||||
| </table> | ||||
|  | ||||
|   | ||||
| @@ -13,29 +13,31 @@ | ||||
| <table class='table table-striped table-condensed'> | ||||
|     <tbody> | ||||
|         {% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-globe" %} | ||||
|     </tbody> | ||||
| </table> | ||||
|  | ||||
| <table class='table table-striped table-condensed'> | ||||
|     <tbody> | ||||
|         <tr> | ||||
|             <td></td> | ||||
|             <th>{% trans "Base Currency" %}</th> | ||||
|             <th>{{ base_currency }}</th> | ||||
|         </tr> | ||||
|         <tr> | ||||
|             <th colspan='2'>{% trans "Exchange Rates" %}</th> | ||||
|             <td></td> | ||||
|             <th colspan='4'>{% trans "Exchange Rates" %}</th> | ||||
|         </tr> | ||||
|         {% for rate in rates %} | ||||
|         <tr> | ||||
|             <td>{{ rate.currency }}</td> | ||||
|             <td></td> | ||||
|             <td>{{ rate.value }}</td> | ||||
|             <td>{{ rate.currency }}</td> | ||||
|             <td></td> | ||||
|             <td></td> | ||||
|         </tr> | ||||
|         {% endfor %} | ||||
|         <tr> | ||||
|             <th></th> | ||||
|             <th> | ||||
|                 {% trans "Last Update" %} | ||||
|             </th> | ||||
|             <td> | ||||
|             <td colspan="3"> | ||||
|                 {% if rates_updated %} | ||||
|                 {{ rates_updated }} | ||||
|                 {% else %} | ||||
| @@ -44,7 +46,7 @@ | ||||
|                 <form action='{% url "settings-currencies-refresh" %}' method='post'> | ||||
|                     <div id='refresh-rates-form'> | ||||
|                         {% csrf_token %} | ||||
|                         <button type='submit' id='update-rates' class='btn btn-outline-secondary float-right'>{% trans "Update Now" %}</button> | ||||
|                         <button type='submit' id='update-rates' class='btn btn-primary float-right'>{% trans "Update Now" %}</button> | ||||
|                     </div> | ||||
|                 </form> | ||||
|             </td> | ||||
|   | ||||
| @@ -17,7 +17,7 @@ | ||||
|         {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_PWD_FORGOT" icon="fa-info-circle" %} | ||||
|         {% include "InvenTree/settings/setting.html" with key="LOGIN_MAIL_REQUIRED" icon="fa-info-circle" %} | ||||
|         <tr> | ||||
|             <td>{% trans 'Signup' %}</td> | ||||
|             <th><h5>{% trans 'Signup' %}</h5></th> | ||||
|             <td colspan='4'></td> | ||||
|         </tr> | ||||
|         {% include "InvenTree/settings/setting.html" with key="LOGIN_ENABLE_REG" icon="fa-info-circle" %} | ||||
|   | ||||
| @@ -9,8 +9,6 @@ | ||||
|  | ||||
| {% block content %} | ||||
|  | ||||
| <h4>{% trans "Part Options" %}</h4> | ||||
|  | ||||
| <table class='table table-striped table-condensed'> | ||||
|     <tbody> | ||||
|         {% include "InvenTree/settings/setting.html" with key="PART_IPN_REGEX" %} | ||||
| @@ -40,12 +38,17 @@ | ||||
|     </tbody> | ||||
| </table> | ||||
|  | ||||
| <h4>{% trans "Part Import" %}</h4> | ||||
|  | ||||
|     <button class='btn btn-success' id='import-part'> | ||||
|         <span class='fas fa-plus-circle'></span> {% trans "Import Part" %} | ||||
|     </button> | ||||
|  | ||||
| <div class='panel-heading'> | ||||
|     <div class='d-flex flex-span'> | ||||
|         <h4>{% trans "Part Import" %}</h4> | ||||
|         {% include "spacer.html" %} | ||||
|         <div class='btn-group' role='group'> | ||||
|             <button class='btn btn-success' id='import-part'> | ||||
|                 <span class='fas fa-plus-circle'></span> {% trans "Import Part" %} | ||||
|             </button>         | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <table class='table table-striped table-condensed'> | ||||
|     <tbody> | ||||
| @@ -53,14 +56,16 @@ | ||||
|     </tbody> | ||||
| </table> | ||||
|  | ||||
|  | ||||
|  | ||||
| <h4>{% trans "Part Parameter Templates" %}</h4> | ||||
|  | ||||
| <div id='param-buttons'> | ||||
|     <button class='btn btn-success' id='new-param'> | ||||
|         <span class='fas fa-plus-circle'></span> {% trans "New Parameter" %} | ||||
|     </button> | ||||
| <div class='panel-heading'> | ||||
|     <span class='d-flex flex-span'> | ||||
|         <h4>{% trans "Part Parameter Templates" %}</h4> | ||||
|         {% include "spacer.html" %} | ||||
|         <div class='btn-group' role='group'> | ||||
|             <button class='btn btn-success' id='new-param'> | ||||
|                 <span class='fas fa-plus-circle'></span> {% trans "New Parameter" %} | ||||
|             </button> | ||||
|         </div> | ||||
|     </span> | ||||
| </div> | ||||
|  | ||||
| <table class='table table-striped table-condensed' id='param-table' data-toolbar='#param-buttons'> | ||||
|   | ||||
| @@ -21,15 +21,13 @@ | ||||
|         </div> | ||||
|         {% else %} | ||||
|         <div id='setting-{{ setting.pk }}'> | ||||
|             <strong> | ||||
|             <span id='setting-value-{{ setting.key.upper }}' fieldname='{{ setting.key.upper }}'> | ||||
|                 {% if setting.value %} | ||||
|                 {{ setting.value }} | ||||
|                 <strong>{{ setting.value }}</strong> | ||||
|                 {% else %} | ||||
|                 <em>{% trans "No value set" %}</em> | ||||
|                 <em style='color: #855;'>{% trans "No value set" %}</em> | ||||
|                 {% endif %} | ||||
|             </span> | ||||
|             </strong> | ||||
|             {{ setting.units }} | ||||
|         </div> | ||||
|         {% endif %} | ||||
|   | ||||
| @@ -11,18 +11,18 @@ | ||||
| {% trans "Account Settings" %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block actions %} | ||||
| <div class='btn btn-primary' type='button' id='edit-user' title='{% trans "Edit User Information" %}'> | ||||
|     <span class='fas fa-user-cog'></span> {% trans "Edit" %} | ||||
| </div> | ||||
| <div class='btn btn-primary' type='button' id='edit-password' title='{% trans "Change Password" %}'> | ||||
|     <span class='fas fa-key'></span> {% trans "Set Password" %} | ||||
| </div> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
| {% mail_configured as mail_conf %} | ||||
|  | ||||
| <div class='btn-group' style='float: right;'> | ||||
|     <div class='btn btn-primary' type='button' id='edit-user' title='{% trans "Edit User Information" %}'> | ||||
|         <span class='fas fa-user-cog'></span> {% trans "Edit" %} | ||||
|     </div> | ||||
|     <div class='btn btn-primary' type='button' id='edit-password' title='{% trans "Change Password" %}'> | ||||
|         <span class='fas fa-key'></span> {% trans "Set Password" %} | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <table class='table table-striped table-condensed'> | ||||
|     <tr> | ||||
|         <td>{% trans "Username" %}</td> | ||||
| @@ -39,61 +39,81 @@ | ||||
| </table> | ||||
|  | ||||
| <div class='panel-heading'> | ||||
|     <h4>{% trans "Email" %}</h4> | ||||
|     <div class='d-flex flex-span'> | ||||
|         <h4>{% trans "Email" %}</h4> | ||||
|         {% include "spacer.html" %} | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div> | ||||
|     {% if user.emailaddress_set.all %} | ||||
|     <p>{% trans 'The following email addresses are associated with your account:' %}</p> | ||||
| <div class='row'> | ||||
|     <div class='col-sm-6'> | ||||
|         {% if user.emailaddress_set.all %} | ||||
|         <p>{% trans 'The following email addresses are associated with your account:' %}</p> | ||||
|  | ||||
|     <form action="{% url 'account_email' %}" class="email_list" method="post"> | ||||
|     {% csrf_token %} | ||||
|         <fieldset class="blockLabels"> | ||||
|         <form action="{% url 'account_email' %}" class="email_list" method="post"> | ||||
|         {% csrf_token %} | ||||
|             <fieldset class="blockLabels"> | ||||
|  | ||||
|         {% for emailaddress in user.emailaddress_set.all %} | ||||
|             <div class="ctrlHolder"> | ||||
|                 <label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}"> | ||||
|  | ||||
|                 <input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/> | ||||
|  | ||||
|             {{ emailaddress.email }} | ||||
|                 {% if emailaddress.verified %} | ||||
|                 <span class="verified">{% trans "Verified" %}</span> | ||||
|                 {% else %} | ||||
|                 <span class="unverified">{% trans "Unverified" %}</span> | ||||
|                 {% endif %} | ||||
|                 {% if emailaddress.primary %}<span class="primary">{% trans "Primary" %}</span>{% endif %} | ||||
|             </label> | ||||
|             {% for emailaddress in user.emailaddress_set.all %} | ||||
|             <div> | ||||
|                 <div class="ctrlHolder"> | ||||
|                     <label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}"> | ||||
|                          | ||||
|                         <input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/> | ||||
|                          | ||||
|                         {% if emailaddress.primary %} | ||||
|                         <b>{{ emailaddress.email }}</b> | ||||
|                         {% else %} | ||||
|                         {{ emailaddress.email }} | ||||
|                         {% endif %} | ||||
|                     </label> | ||||
|                     {% if emailaddress.verified %} | ||||
|                     <span class='badge badge-right rounded-pill bg-success'>{% trans "Verified" %}</span> | ||||
|                     {% else %} | ||||
|                     <span class='badge badge-right rounded-pill bg-warning'>{% trans "Unverified" %}</span> | ||||
|                     {% endif %} | ||||
|                     {% if emailaddress.primary %}<span class='badge badge-right rounded-pill bg-primary'>{% trans "Primary" %}</span>{% endif %} | ||||
|                 </div> | ||||
|             </div> | ||||
|         {% endfor %} | ||||
|             {% endfor %} | ||||
|  | ||||
|         <div class="buttonHolder"> | ||||
|             <button class="btn btn-primary secondaryAction" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button> | ||||
|             <button class="btn btn-primary secondaryAction" type="submit" name="action_send" {% if not mail_conf %}disabled{% endif %}>{% trans 'Re-send Verification' %}</button> | ||||
|             <button class="btn btn-primary primaryAction" type="submit" name="action_remove" >{% trans 'Remove' %}</button> | ||||
|         </div> | ||||
|             <div class="buttonHolder"> | ||||
|                 <button class="btn btn-primary secondaryAction" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button> | ||||
|                 <button class="btn btn-primary secondaryAction" type="submit" name="action_send" {% if not mail_conf %}disabled{% endif %}>{% trans 'Re-send Verification' %}</button> | ||||
|                 <button class="btn btn-primary primaryAction" type="submit" name="action_remove" >{% trans 'Remove' %}</button> | ||||
|             </div> | ||||
|  | ||||
|         </fieldset> | ||||
|     </form> | ||||
|             </fieldset> | ||||
|         </form> | ||||
|  | ||||
|     {% else %} | ||||
|     <p><strong>{% trans 'Warning:'%}</strong> | ||||
|         {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %} | ||||
|     </p> | ||||
|         {% else %} | ||||
|         <p><strong>{% trans 'Warning:'%}</strong> | ||||
|             {% trans "You currently do not have any email address set up. You should really add an email address so you can receive notifications, reset your password, etc." %} | ||||
|         </p> | ||||
|  | ||||
|     {% endif %} | ||||
|  | ||||
|     {% if can_add_email %} | ||||
|         <br> | ||||
|         <h4>{% trans "Add Email Address" %}</h4> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|     <div class='col-sm-6'> | ||||
|         {% if can_add_email %} | ||||
|         <h5>{% trans "Add Email Address" %}</h5> | ||||
|  | ||||
|         <form method="post" action="{% url 'account_email' %}" class="add_email"> | ||||
|             {% csrf_token %} | ||||
|             {{ add_email_form|crispy }} | ||||
|             <button class="btn btn-primary" name="action_add" type="submit">{% trans "Add Email" %}</button> | ||||
|  | ||||
|             <label for="id_email" class=" requiredField"> | ||||
|                 E-mail<span class="asteriskField">*</span> | ||||
|             </label> | ||||
|             <div id="div_id_email" class="form-group input-group mb-3"> | ||||
|                 <div class='input-group-prepend'><span class='input-group-text'>@</span></div> | ||||
|                 <input type="email" name="email" placeholder='{% trans "Enter e-mail address" %}' class="textinput textInput form-control" required="" id="id_email"> | ||||
|                 <div class='input-group-append'> | ||||
|                     <button class="btn btn-primary" name="action_add" type="submit">{% trans "Add Email" %}</button> | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|         </form> | ||||
|     {% endif %} | ||||
|     <br> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div class='panel-heading'> | ||||
| @@ -135,7 +155,9 @@ | ||||
|     </form> | ||||
|      | ||||
|     {% else %} | ||||
|     <p>{% trans 'You currently have no social network accounts connected to this account.' %}</p> | ||||
|     <div class='alert alert-block alert-warning'> | ||||
|         {% trans "There are no social network accounts connected to your InvenTree account" %} | ||||
|     </div> | ||||
|     {% endif %} | ||||
|  | ||||
|     <br> | ||||
| @@ -155,26 +177,26 @@ | ||||
|  | ||||
| <div class='row'> | ||||
|  | ||||
|     <form action='{% url "settings-appearance" %}' method='post'> | ||||
|         {% csrf_token %} | ||||
|         <input name='next' type='hidden' value='{% url "settings" %}'> | ||||
|         <div class="col-sm-6" style="width: 200px;"> | ||||
|             <div id="div_id_themes" class="form-group"> | ||||
|                 <div class="controls "> | ||||
|                     <select name='theme' class='select form-control'> | ||||
|                         {% get_available_themes as themes %} | ||||
|                         {% for theme in themes %} | ||||
|                         <option value='{{ theme.key }}'>{{ theme.name }}</option> | ||||
|                         {% endfor %} | ||||
|                     </select> | ||||
|     <div class='col-sm-6'> | ||||
|         <form action='{% url "settings-appearance" %}' method='post'> | ||||
|             {% csrf_token %} | ||||
|             <input name='next' type='hidden' value='{% url "settings" %}'> | ||||
|             <label for='theme' class=' requiredField'> | ||||
|                 {% trans "Select theme" %} | ||||
|             </label> | ||||
|             <div class='form-group input-group mb-3'> | ||||
|                 <select id='theme' name='theme' class='select form-control'> | ||||
|                     {% get_available_themes as themes %} | ||||
|                     {% for theme in themes %} | ||||
|                     <option value='{{ theme.key }}'>{{ theme.name }}</option> | ||||
|                     {% endfor %} | ||||
|                 </select> | ||||
|                 <div class='input-group-append'> | ||||
|                     <input type="submit" value="{% trans 'Set Theme' %}" class="btn btn-primary"> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="col-sm-6" style="width: auto;"> | ||||
|             <input type="submit" value="{% trans 'Set Theme' %}" class="btn btn btn-primary"> | ||||
|         </div> | ||||
|     </form> | ||||
|  | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <div class='panel-heading'> | ||||
| @@ -186,7 +208,10 @@ | ||||
|         <form action="{% url 'set_language' %}" method="post"> | ||||
|             {% csrf_token %} | ||||
|             <input name="next" type="hidden" value="{% url 'settings' %}"> | ||||
|             <div class="col-sm-6" style="width: 200px;"><div id="div_id_language" class="form-group"><div class="controls "> | ||||
|             <label for='language' class=' requiredField'> | ||||
|                 {% trans "Select language" %} | ||||
|             </label> | ||||
|             <div class='form-group input-group mb-3'> | ||||
|                 <select name="language" class="select form-control"> | ||||
|                     {% get_current_language as LANGUAGE_CODE %} | ||||
|                     {% get_available_languages as LANGUAGES %} | ||||
| @@ -204,11 +229,11 @@ | ||||
|                         </option> | ||||
|                     {% endfor %} | ||||
|                 </select> | ||||
|             </div></div></div> | ||||
|             <div class="col-sm-6" style="width: auto;"> | ||||
|                 <input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary"> | ||||
|                 <div class='input-group-append'> | ||||
|                     <input type="submit" value="{% trans 'Set Language' %}" class="btn btn btn-primary"> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </form> | ||||
|     </div> | ||||
|     <div class="col-sm-6"> | ||||
|         <h4>{% trans "Help the translation efforts!" %}</h4> | ||||
|   | ||||
							
								
								
									
										43
									
								
								InvenTree/templates/email/email.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								InvenTree/templates/email/email.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| {% load i18n %} | ||||
| {% load static %} | ||||
| {% load inventree_extras %} | ||||
|  | ||||
| <table style='border-collapse: collapse; width: 85%; margin-left: 10%; font-size: 1rem; border: 1px solid #68686a; border-radius: 2px;'> | ||||
|  | ||||
|     {% block header %} | ||||
|     <tr style='background: #eef3f7; height: 4rem; text-align: center;'> | ||||
|         <th colspan="100%" style="padding-bottom: 1rem; color: #68686a;"> | ||||
|             {% block header_row %} | ||||
|             <p style='font-size: 1.25rem;'>{% block title %}<!-- email title goes here -->{% endblock %}</p> | ||||
|             {% block subtitle %} | ||||
|             <!-- email subtitle goes here --> | ||||
|             {% endblock %} | ||||
|             {% endblock %} | ||||
|         </th> | ||||
|  | ||||
|     </tr> | ||||
|     {% endblock %} | ||||
|  | ||||
|     {% block body %} | ||||
|     <tr style="height: 3rem; border-bottom: 1px solid #68686a;"> | ||||
|         {% block body_row %} | ||||
|         <!-- email body goes here --> | ||||
|         {% endblock %} | ||||
|     </tr> | ||||
|     {% endblock %} | ||||
|  | ||||
|     {% block footer %} | ||||
|     <tr style='background: #eef3f7; height: 2rem;'> | ||||
|         <td colspan="100%" style="padding-top:1rem; text-align: center"> | ||||
|             {% block footer_prefix %} | ||||
|             <!-- Custom footer information goes here --> | ||||
|             {% endblock %} | ||||
|             <p><em><small>{% trans "InvenTree version" %}: {% inventree_version %} - <a href='https://inventree.readthedocs.io'>inventree.readthedocs.io</a></small></em></p> | ||||
|             {% block footer_suffix %} | ||||
|             <!-- Custom footer information goes here --> | ||||
|             {% endblock %} | ||||
|         </td> | ||||
|     </tr> | ||||
|     {% endblock %} | ||||
|  | ||||
| </table> | ||||
							
								
								
									
										29
									
								
								InvenTree/templates/email/low_stock_notification.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								InvenTree/templates/email/low_stock_notification.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| {% extends "email/email.html" %} | ||||
|  | ||||
| {% load i18n %} | ||||
| {% load inventree_extras %} | ||||
|  | ||||
| {% block title %} | ||||
| {% blocktrans with part=part.name %} The available stock for {{ part }} has fallen below the configured minimum level{% endblocktrans %} | ||||
| {% if link %} | ||||
| <p>{% trans "Click on the following link to view this part" %}: <a href="{{ link }}">{{ link }}</a></p> | ||||
| {% endif %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block subtitle %} | ||||
| <p><em>{% blocktrans with part=part.name %}You are receiving this email because you are subscribed to notifications for this part {% endblocktrans %}.</em></p> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block body %} | ||||
| <tr style="height: 3rem; border-bottom: 1px solid"> | ||||
|     <th>{% trans "Part Name" %}</th> | ||||
|     <th>{% trans "Available Quantity" %}</th> | ||||
|     <th>{% trans "Minimum Quantity" %}</th> | ||||
| </tr> | ||||
|  | ||||
| <tr style="height: 3rem"> | ||||
|     <td style="text-align: center;">{{ part.full_name }}</td> | ||||
|     <td style="text-align: center;">{{ part.total_stock }}</td> | ||||
|     <td style="text-align: center;">{{ part.minimum_stock }}</td> | ||||
| </tr> | ||||
| {% endblock %} | ||||
| @@ -1133,8 +1133,10 @@ function loadPartTable(table, url, options={}) { | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Display a table of part categories | ||||
|  */ | ||||
| function loadPartCategoryTable(table, options) { | ||||
|     /* Display a table of part categories */ | ||||
|  | ||||
|     var params = options.params || {}; | ||||
|  | ||||
| @@ -1157,15 +1159,15 @@ function loadPartCategoryTable(table, options) { | ||||
|  | ||||
|     setupFilterList(filterKey, table, filterListElement); | ||||
|  | ||||
|     var tree_view = inventreeLoad('category-tree-view') == 1; | ||||
|     var tree_view = options.allowTreeView && inventreeLoad('category-tree-view') == 1; | ||||
|  | ||||
|     table.inventreeTable({ | ||||
|         treeEnable: tree_view, | ||||
|         rootParentId: options.params.parent, | ||||
|         rootParentId: tree_view ? options.params.parent : null, | ||||
|         uniqueId: 'pk', | ||||
|         idField: 'pk', | ||||
|         treeShowField: 'name', | ||||
|         parentIdField: 'parent', | ||||
|         parentIdField: tree_view ? 'parent' : null, | ||||
|         method: 'get', | ||||
|         url: options.url || '{% url "api-part-category-list" %}', | ||||
|         queryParams: filters, | ||||
| @@ -1176,7 +1178,7 @@ function loadPartCategoryTable(table, options) { | ||||
|         name: 'category', | ||||
|         original: original, | ||||
|         showColumns: true, | ||||
|         buttons: [ | ||||
|         buttons: options.allowTreeView ? [ | ||||
|             { | ||||
|                 icon: 'fas fa-bars', | ||||
|                 attributes: { | ||||
| @@ -1215,28 +1217,31 @@ function loadPartCategoryTable(table, options) { | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         ], | ||||
|         ] : [], | ||||
|         onPostBody: function() { | ||||
|  | ||||
|             tree_view = inventreeLoad('category-tree-view') == 1; | ||||
|             if (options.allowTreeView) { | ||||
|  | ||||
|             if (tree_view) { | ||||
|                 tree_view = inventreeLoad('category-tree-view') == 1; | ||||
|  | ||||
|                 $('#view-category-list').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                 $('#view-category-tree').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                  | ||||
|                 table.treegrid({ | ||||
|                     treeColumn: 0, | ||||
|                     onChange: function() { | ||||
|                         table.bootstrapTable('resetView'); | ||||
|                     }, | ||||
|                     onExpand: function() { | ||||
|                          | ||||
|                     } | ||||
|                 }); | ||||
|             } else { | ||||
|                 $('#view-category-tree').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                 $('#view-category-list').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                 if (tree_view) { | ||||
|  | ||||
|                     $('#view-category-list').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                     $('#view-category-tree').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                      | ||||
|                     table.treegrid({ | ||||
|                         treeColumn: 0, | ||||
|                         onChange: function() { | ||||
|                             table.bootstrapTable('resetView'); | ||||
|                         }, | ||||
|                         onExpand: function() { | ||||
|                              | ||||
|                         } | ||||
|                     }); | ||||
|                 } else { | ||||
|                     $('#view-category-tree').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                     $('#view-category-list').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         columns: [ | ||||
|   | ||||
| @@ -1416,8 +1416,11 @@ function loadStockTable(table, options) { | ||||
|     }); | ||||
| } | ||||
|  | ||||
|  | ||||
| /*  | ||||
|  * Display a table of stock locations | ||||
|  */ | ||||
| function loadStockLocationTable(table, options) { | ||||
|     /* Display a table of stock locations */ | ||||
|  | ||||
|     var params = options.params || {}; | ||||
|  | ||||
| @@ -1443,15 +1446,15 @@ function loadStockLocationTable(table, options) { | ||||
|         filters[key] = params[key]; | ||||
|     } | ||||
|  | ||||
|     var tree_view = inventreeLoad('location-tree-view') == 1; | ||||
|     var tree_view = options.allowTreeView && inventreeLoad('location-tree-view') == 1; | ||||
|  | ||||
|     table.inventreeTable({ | ||||
|         treeEnable: tree_view, | ||||
|         rootParentId: options.params.parent, | ||||
|         rootParentId: tree_view ? options.params.parent : null, | ||||
|         uniqueId: 'pk', | ||||
|         idField: 'pk', | ||||
|         treeShowField: 'name', | ||||
|         parentIdField: 'parent', | ||||
|         parentIdField: tree_view ? 'parent' : null, | ||||
|         disablePagination: tree_view, | ||||
|         sidePagination: tree_view ? 'client' : 'server', | ||||
|         serverSort: !tree_view, | ||||
| @@ -1465,28 +1468,31 @@ function loadStockLocationTable(table, options) { | ||||
|         showColumns: true, | ||||
|         onPostBody: function() { | ||||
|  | ||||
|             tree_view = inventreeLoad('location-tree-view') == 1; | ||||
|             if (options.allowTreeView) { | ||||
|  | ||||
|             if (tree_view) { | ||||
|                 tree_view = inventreeLoad('location-tree-view') == 1; | ||||
|  | ||||
|                 $('#view-location-list').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                 $('#view-location-tree').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                  | ||||
|                 table.treegrid({ | ||||
|                     treeColumn: 1, | ||||
|                     onChange: function() { | ||||
|                         table.bootstrapTable('resetView'); | ||||
|                     }, | ||||
|                     onExpand: function() { | ||||
|                          | ||||
|                     } | ||||
|                 }); | ||||
|             } else { | ||||
|                 $('#view-location-tree').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                 $('#view-location-list').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                 if (tree_view) { | ||||
|  | ||||
|                     $('#view-location-list').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                     $('#view-location-tree').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                      | ||||
|                     table.treegrid({ | ||||
|                         treeColumn: 1, | ||||
|                         onChange: function() { | ||||
|                             table.bootstrapTable('resetView'); | ||||
|                         }, | ||||
|                         onExpand: function() { | ||||
|                              | ||||
|                         } | ||||
|                     }); | ||||
|                 } else { | ||||
|                     $('#view-location-tree').removeClass('btn-secondary').addClass('btn-outline-secondary'); | ||||
|                     $('#view-location-list').removeClass('btn-outline-secondary').addClass('btn-secondary'); | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         buttons: [ | ||||
|         buttons: options.allowTreeView ? [ | ||||
|             { | ||||
|                 icon: 'fas fa-bars', | ||||
|                 attributes: { | ||||
| @@ -1525,7 +1531,7 @@ function loadStockLocationTable(table, options) { | ||||
|                     ); | ||||
|                 } | ||||
|             } | ||||
|         ], | ||||
|         ] : [], | ||||
|         columns: [ | ||||
|             { | ||||
|                 checkbox: true, | ||||
|   | ||||
| @@ -12,7 +12,10 @@ | ||||
|     <div class="navbar-header clearfix content-heading"> | ||||
|       <a class="navbar-brand" id='logo' href="{% url 'index' %}" style="padding-top: 7px; padding-bottom: 5px;"><img src="{% static 'img/inventree.png' %}" width="32" height="32" style="display:block; margin: auto;"/></a> | ||||
|     </div> | ||||
|     <div class="navbar-collapse collapse"> | ||||
|     <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar-objects" aria-label="Toggle navigation"> | ||||
|       <span class="navbar-toggler-icon"></span> | ||||
|     </button> | ||||
|     <div class="navbar-collapse collapse" id="navbar-objects"> | ||||
|       <ul class="navbar-nav"> | ||||
|         {% if roles.part.view %} | ||||
|         <li class='nav-item'> | ||||
| @@ -130,3 +133,7 @@ | ||||
|  | ||||
|   </div> | ||||
| </nav> | ||||
|  | ||||
| {% if sticky %} | ||||
| <div class='navbar-spacer'></div> | ||||
| {% endif %} | ||||
|   | ||||
| @@ -57,4 +57,4 @@ | ||||
| {% block page_content %} | ||||
| {% endblock %} | ||||
|  | ||||
| {% endblock %} | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -1,7 +1,14 @@ | ||||
| <div class='panel panel-hidden' id='panel-{% block label %}name{% endblock %}'> | ||||
|     {% block panel_heading %} | ||||
|     <div class='panel-heading'> | ||||
|         <h4>{% block heading %}HEADING{% endblock %}</h4> | ||||
|         <div class='d-flex flex-wrap'> | ||||
|             <h4>{% block heading %}HEADING{% endblock %}</h4> | ||||
|             {% include "spacer.html" %} | ||||
|             <div class='btn-group' role='group'> | ||||
|                 {% block actions %} | ||||
|                 {% endblock %} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     {% endblock %} | ||||
|     {% block panel_content %} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user