mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
f33bae3918
@ -44,8 +44,9 @@ class InvenTreeExchange(SimpleExchangeBackend):
|
|||||||
response = urlopen(url, timeout=5, context=context)
|
response = urlopen(url, timeout=5, context=context)
|
||||||
return response.read()
|
return response.read()
|
||||||
except Exception:
|
except Exception:
|
||||||
# Returning None here will raise an error upstream
|
# Something has gone wrong, but we can just try again next time
|
||||||
return None
|
# Raise a TypeError so the outer function can handle this
|
||||||
|
raise TypeError
|
||||||
|
|
||||||
def update_rates(self, base_currency=None):
|
def update_rates(self, base_currency=None):
|
||||||
"""Set the requested currency codes and get rates."""
|
"""Set the requested currency codes and get rates."""
|
||||||
@ -60,6 +61,8 @@ class InvenTreeExchange(SimpleExchangeBackend):
|
|||||||
# catch connection errors
|
# catch connection errors
|
||||||
except URLError:
|
except URLError:
|
||||||
print('Encountered connection error while updating')
|
print('Encountered connection error while updating')
|
||||||
|
except TypeError:
|
||||||
|
print('Exchange returned invalid response')
|
||||||
except OperationalError as e:
|
except OperationalError as e:
|
||||||
if 'SerializationFailure' in e.__cause__.__class__.__name__:
|
if 'SerializationFailure' in e.__cause__.__class__.__name__:
|
||||||
print('Serialization Failure while updating exchange rates')
|
print('Serialization Failure while updating exchange rates')
|
||||||
|
1
InvenTree/InvenTree/static/script/qr-scanner.min.js.map
Normal file
1
InvenTree/InvenTree/static/script/qr-scanner.min.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -32,6 +32,7 @@ import InvenTree.tasks
|
|||||||
|
|
||||||
from plugin.events import trigger_event
|
from plugin.events import trigger_event
|
||||||
|
|
||||||
|
import common.notifications
|
||||||
from part import models as PartModels
|
from part import models as PartModels
|
||||||
from stock import models as StockModels
|
from stock import models as StockModels
|
||||||
from users import models as UserModels
|
from users import models as UserModels
|
||||||
@ -534,12 +535,51 @@ class Build(MPTTModel, ReferenceIndexingMixin):
|
|||||||
self.subtract_allocated_stock(user)
|
self.subtract_allocated_stock(user)
|
||||||
|
|
||||||
# Ensure that there are no longer any BuildItem objects
|
# Ensure that there are no longer any BuildItem objects
|
||||||
# which point to thisFcan Build Order
|
# which point to this Build Order
|
||||||
self.allocated_stock.all().delete()
|
self.allocated_stock.all().delete()
|
||||||
|
|
||||||
# Register an event
|
# Register an event
|
||||||
trigger_event('build.completed', id=self.pk)
|
trigger_event('build.completed', id=self.pk)
|
||||||
|
|
||||||
|
# Notify users that this build has been completed
|
||||||
|
targets = [
|
||||||
|
self.issued_by,
|
||||||
|
self.responsible,
|
||||||
|
]
|
||||||
|
|
||||||
|
# Notify those users interested in the parent build
|
||||||
|
if self.parent:
|
||||||
|
targets.append(self.parent.issued_by)
|
||||||
|
targets.append(self.parent.responsible)
|
||||||
|
|
||||||
|
# Notify users if this build points to a sales order
|
||||||
|
if self.sales_order:
|
||||||
|
targets.append(self.sales_order.created_by)
|
||||||
|
targets.append(self.sales_order.responsible)
|
||||||
|
|
||||||
|
build = self
|
||||||
|
name = _(f'Build order {build} has been completed')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'build': build,
|
||||||
|
'name': name,
|
||||||
|
'slug': 'build.completed',
|
||||||
|
'message': _('A build order has been completed'),
|
||||||
|
'link': InvenTree.helpers.construct_absolute_url(self.get_absolute_url()),
|
||||||
|
'template': {
|
||||||
|
'html': 'email/build_order_completed.html',
|
||||||
|
'subject': name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
common.notifications.trigger_notification(
|
||||||
|
build,
|
||||||
|
'build.completed',
|
||||||
|
targets=targets,
|
||||||
|
context=context,
|
||||||
|
target_exclude=[user],
|
||||||
|
)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def cancel_build(self, user, **kwargs):
|
def cancel_build(self, user, **kwargs):
|
||||||
"""Mark the Build as CANCELLED.
|
"""Mark the Build as CANCELLED.
|
||||||
|
@ -291,7 +291,7 @@ class InvenTreeNotificationBodies:
|
|||||||
NewOrder = NotificationBody(
|
NewOrder = NotificationBody(
|
||||||
name=_("New {verbose_name}"),
|
name=_("New {verbose_name}"),
|
||||||
slug='{app_label}.new_{model_name}',
|
slug='{app_label}.new_{model_name}',
|
||||||
message=_("A new {verbose_name} has been created and ,assigned to you"),
|
message=_("A new order has been created and assigned to you"),
|
||||||
template='email/new_order_assigned.html',
|
template='email/new_order_assigned.html',
|
||||||
)
|
)
|
||||||
"""Send when a new order (build, sale or purchase) was created."""
|
"""Send when a new order (build, sale or purchase) was created."""
|
||||||
@ -344,8 +344,10 @@ def trigger_notification(obj, category=None, obj_ref='pk', **kwargs):
|
|||||||
|
|
||||||
if targets:
|
if targets:
|
||||||
for target in targets:
|
for target in targets:
|
||||||
|
if target is None:
|
||||||
|
continue
|
||||||
# User instance is provided
|
# User instance is provided
|
||||||
if isinstance(target, get_user_model()):
|
elif isinstance(target, get_user_model()):
|
||||||
if target not in target_exclude:
|
if target not in target_exclude:
|
||||||
target_users.add(target)
|
target_users.add(target)
|
||||||
# Group instance is provided
|
# Group instance is provided
|
||||||
|
27
InvenTree/templates/email/build_order_completed.html
Normal file
27
InvenTree/templates/email/build_order_completed.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "email/email.html" %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
{% load inventree_extras %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ message }}
|
||||||
|
{% if link %}
|
||||||
|
<p>{% trans "Click on the following link to view this order" %}: <a href="{{ link }}">{{ link }}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock title %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
|
||||||
|
<tr style="height: 3rem; border-bottom: 1px solid">
|
||||||
|
<th>{% trans "Build Order" %}</th>
|
||||||
|
<th>{% trans "Part" %}</th>
|
||||||
|
<th>{% trans "Quantity" %}</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="height: 3rem">
|
||||||
|
<td style="text-align: center;">{{ build }}</td>
|
||||||
|
<td style="text-align: center;">{{ build.part.full_name }}</td>
|
||||||
|
<td style="text-align: center;">{{ build.quantity }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% endblock body %}
|
@ -938,7 +938,7 @@ function getFormFieldElement(name, options) {
|
|||||||
/*
|
/*
|
||||||
* Check that a "numerical" input field has a valid number in it.
|
* Check that a "numerical" input field has a valid number in it.
|
||||||
* An invalid number is expunged at the client side by the getFormFieldValue() function,
|
* An invalid number is expunged at the client side by the getFormFieldValue() function,
|
||||||
* which means that an empty string '' is sent to the server if the number is not valud.
|
* which means that an empty string '' is sent to the server if the number is not valid.
|
||||||
* This can result in confusing error messages displayed under the form field.
|
* This can result in confusing error messages displayed under the form field.
|
||||||
*
|
*
|
||||||
* So, we can invalid numbers and display errors *before* the form is submitted!
|
* So, we can invalid numbers and display errors *before* the form is submitted!
|
||||||
@ -947,7 +947,8 @@ function validateFormField(name, options) {
|
|||||||
|
|
||||||
if (getFormFieldElement(name, options)) {
|
if (getFormFieldElement(name, options)) {
|
||||||
|
|
||||||
var el = document.getElementById(`id_${name}`);
|
var field_name = getFieldName(name, options);
|
||||||
|
var el = document.getElementById(`id_${field_name}`);
|
||||||
|
|
||||||
if (el.validity.valueMissing) {
|
if (el.validity.valueMissing) {
|
||||||
// Accept empty strings (server will validate)
|
// Accept empty strings (server will validate)
|
||||||
|
@ -31,6 +31,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trim the supplied string to ensure the string length is limited to the provided value
|
||||||
|
*/
|
||||||
|
function trim(data, max_length=100) {
|
||||||
|
if (data.length > max_length) {
|
||||||
|
data = data.slice(0, max_length - 3) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Should the ID be rendered for this string
|
// Should the ID be rendered for this string
|
||||||
function renderId(title, pk, parameters={}) {
|
function renderId(title, pk, parameters={}) {
|
||||||
|
|
||||||
@ -55,7 +67,7 @@ function renderCompany(name, data, parameters={}, options={}) {
|
|||||||
|
|
||||||
var html = select2Thumbnail(data.image);
|
var html = select2Thumbnail(data.image);
|
||||||
|
|
||||||
html += `<span><b>${data.name}</b></span> - <i>${data.description}</i>`;
|
html += `<span><b>${data.name}</b></span> - <i>${trim(data.description)}</i>`;
|
||||||
|
|
||||||
html += renderId('{% trans "Company ID" %}', data.pk, parameters);
|
html += renderId('{% trans "Company ID" %}', data.pk, parameters);
|
||||||
|
|
||||||
@ -141,7 +153,7 @@ function renderStockLocation(name, data, parameters={}, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (render_description && data.description) {
|
if (render_description && data.description) {
|
||||||
html += ` - <i>${data.description}</i>`;
|
html += ` - <i>${trim(data.description)}</i>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += renderId('{% trans "Location ID" %}', data.pk, parameters);
|
html += renderId('{% trans "Location ID" %}', data.pk, parameters);
|
||||||
@ -177,7 +189,7 @@ function renderPart(name, data, parameters={}, options={}) {
|
|||||||
html += ` <span>${data.full_name || data.name}</span>`;
|
html += ` <span>${data.full_name || data.name}</span>`;
|
||||||
|
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
html += ` - <i><small>${data.description}</small></i>`;
|
html += ` - <i><small>${trim(data.description)}</small></i>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stock_data = '';
|
var stock_data = '';
|
||||||
@ -256,7 +268,7 @@ function renderPurchaseOrder(name, data, parameters={}, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
html += ` - <em>${data.description}</em>`;
|
html += ` - <em>${trim(data.description)}</em>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += renderId('{% trans "Order ID" %}', data.pk, parameters);
|
html += renderId('{% trans "Order ID" %}', data.pk, parameters);
|
||||||
@ -282,7 +294,7 @@ function renderSalesOrder(name, data, parameters={}, options={}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
html += ` - <em>${data.description}</em>`;
|
html += ` - <em>${trim(data.description)}</em>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += renderId('{% trans "Order ID" %}', data.pk, parameters);
|
html += renderId('{% trans "Order ID" %}', data.pk, parameters);
|
||||||
@ -319,7 +331,7 @@ function renderPartCategory(name, data, parameters={}, options={}) {
|
|||||||
var html = `<span>${level}${data.pathstring}</span>`;
|
var html = `<span>${level}${data.pathstring}</span>`;
|
||||||
|
|
||||||
if (data.description) {
|
if (data.description) {
|
||||||
html += ` - <i>${data.description}</i>`;
|
html += ` - <i>${trim(data.description)}</i>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += renderId('{% trans "Category ID" %}', data.pk, parameters);
|
html += renderId('{% trans "Category ID" %}', data.pk, parameters);
|
||||||
|
Loading…
Reference in New Issue
Block a user