diff --git a/InvenTree/company/models.py b/InvenTree/company/models.py
index b6af76a022..518bc41f77 100644
--- a/InvenTree/company/models.py
+++ b/InvenTree/company/models.py
@@ -235,6 +235,11 @@ class Contact(models.Model):
role: position in company
"""
+ @staticmethod
+ def get_api_url():
+ """Return the API URL associated with the Contcat model"""
+ return reverse('api-contact-list')
+
company = models.ForeignKey(Company, related_name='contacts',
on_delete=models.CASCADE)
diff --git a/InvenTree/order/models.py b/InvenTree/order/models.py
index a9fd1742bc..fe8c7ab442 100644
--- a/InvenTree/order/models.py
+++ b/InvenTree/order/models.py
@@ -170,6 +170,18 @@ class Order(MetadataMixin, ReferenceIndexingMixin):
super().save(*args, **kwargs)
+ def clean(self):
+ """Custom clean method for the generic order class"""
+
+ super().clean()
+
+ # Check that the referenced 'contact' matches the correct 'company'
+ if self.company and self.contact:
+ if self.contact.company != self.company:
+ raise ValidationError({
+ "contact": _("Contact does not match selected company")
+ })
+
description = models.CharField(max_length=250, verbose_name=_('Description'), help_text=_('Order description'))
link = InvenTreeURLField(blank=True, verbose_name=_('Link'), help_text=_('Link to external page'))
@@ -305,6 +317,11 @@ class PurchaseOrder(TotalPriceMixin, Order):
help_text=_('Company from which the items are being ordered')
)
+ @property
+ def company(self):
+ """Accessor helper for Order base class"""
+ return self.suplier
+
supplier_reference = models.CharField(max_length=64, blank=True, verbose_name=_('Supplier Reference'), help_text=_("Supplier order reference code"))
received_by = models.ForeignKey(
@@ -711,6 +728,11 @@ class SalesOrder(TotalPriceMixin, Order):
help_text=_("Company to which the items are being sold"),
)
+ @property
+ def company(self):
+ """Accessor helper for Order base"""
+ return self.customer
+
status = models.PositiveIntegerField(
default=SalesOrderStatus.PENDING,
choices=SalesOrderStatus.items(),
@@ -1626,6 +1648,11 @@ class ReturnOrder(Order):
help_text=_("Company from which items are being returned"),
)
+ @property
+ def company(self):
+ """Accessor helper for Order base class"""
+ return self.customer
+
status = models.PositiveIntegerField(
default=ReturnOrderStatus.PENDING,
choices=ReturnOrderStatus.items(),
diff --git a/InvenTree/order/serializers.py b/InvenTree/order/serializers.py
index 8aa59ccb5c..5b5ab1260f 100644
--- a/InvenTree/order/serializers.py
+++ b/InvenTree/order/serializers.py
@@ -17,7 +17,8 @@ import order.models
import part.filters
import stock.models
import stock.serializers
-from company.serializers import CompanyBriefSerializer, SupplierPartSerializer
+from company.serializers import (CompanyBriefSerializer, ContactSerializer,
+ SupplierPartSerializer)
from InvenTree.helpers import extract_serial_numbers, normalize, str2bool
from InvenTree.serializers import (InvenTreeAttachmentSerializer,
InvenTreeCurrencySerializer,
@@ -1411,6 +1412,8 @@ class ReturnOrderSerializer(InvenTreeModelSerializer):
fields = [
'pk',
'creation_date',
+ 'contact',
+ 'contact_detail',
'customer',
'customer_detail',
'customer_reference',
@@ -1446,6 +1449,8 @@ class ReturnOrderSerializer(InvenTreeModelSerializer):
# TODO
return queryset
+ contact_detail = ContactSerializer(source='contact', many=False, read_only=True)
+
customer_detail = CompanyBriefSerializer(source='customer', many=False, read_only=True)
status_text = serializers.CharField(source='get_status_display', read_only=True)
diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html
index 15278bfbb3..6c84cb022c 100644
--- a/InvenTree/order/templates/order/sales_order_base.html
+++ b/InvenTree/order/templates/order/sales_order_base.html
@@ -204,12 +204,13 @@ src="{% static 'img/blank_image.png' %}"
{% block js_ready %}
{{ block.super }}
+{% if roles.sales_order.change %}
$("#edit-order").click(function() {
-
editSalesOrder({{ order.pk }}, {
reload: true,
});
});
+{% endif %}
$("#complete-order-shipments").click(function() {
diff --git a/InvenTree/templates/js/translated/forms.js b/InvenTree/templates/js/translated/forms.js
index 800a587f8c..88bdd8d44c 100644
--- a/InvenTree/templates/js/translated/forms.js
+++ b/InvenTree/templates/js/translated/forms.js
@@ -2044,6 +2044,9 @@ function renderModelData(name, model, data, parameters, options) {
case 'company':
renderer = renderCompany;
break;
+ case 'contact':
+ renderer = renderContact;
+ break;
case 'stockitem':
renderer = renderStockItem;
break;
diff --git a/InvenTree/templates/js/translated/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js
index fb38e1345b..b8d0e8c76c 100644
--- a/InvenTree/templates/js/translated/model_renderers.js
+++ b/InvenTree/templates/js/translated/model_renderers.js
@@ -8,6 +8,7 @@
/* exported
renderBuild,
renderCompany,
+ renderContact,
renderGroup,
renderManufacturerPart,
renderOwner,
@@ -67,7 +68,7 @@ function renderId(title, pk, parameters={}) {
// eslint-disable-next-line no-unused-vars
function renderCompany(name, data, parameters={}, options={}) {
- var html = select2Thumbnail(data.image);
+ let html = select2Thumbnail(data.image);
html += `${data.name} - ${trim(data.description)}`;
@@ -77,6 +78,21 @@ function renderCompany(name, data, parameters={}, options={}) {
}
+// Renderer for the "Contact" model
+// eslint-disable-next-line no-unused-vars
+function renderContact(name, data, parameters={}, options={}) {
+ let html = `${data.name}`;
+
+ if (data.email) {
+ html += `- ${data.email}`;
+
+ html += renderId('{% trans "Contact ID" %}', data.pk, parameters);
+ }
+
+ return html;
+}
+
+
// Renderer for "StockItem" model
// eslint-disable-next-line no-unused-vars
function renderStockItem(name, data, parameters={}, options={}) {
@@ -138,7 +154,7 @@ function renderStockItem(name, data, parameters={}, options={}) {
}
}
- var html = `
+ let html = `
${part_detail}
${stock_detail}
@@ -157,7 +173,7 @@ function renderStockLocation(name, data, parameters={}, options={}) {
var level = '- '.repeat(data.level);
- var html = `${level}${data.pathstring}`;
+ let html = `${level}${data.pathstring}`;
var render_description = true;
@@ -183,7 +199,7 @@ function renderBuild(name, data, parameters={}, options={}) {
image = data.part_detail.thumbnail;
}
- var html = select2Thumbnail(image);
+ let html = select2Thumbnail(image);
html += `${data.reference} - ${data.quantity} x ${data.part_detail.full_name}`;
@@ -197,7 +213,7 @@ function renderBuild(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderPart(name, data, parameters={}, options={}) {
- var html = select2Thumbnail(data.image);
+ let html = select2Thumbnail(data.image);
html += ` ${data.full_name || data.name}`;
@@ -234,7 +250,7 @@ function renderPart(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderGroup(name, data, parameters={}, options={}) {
- var html = `${data.name}`;
+ let html = `${data.name}`;
return html;
@@ -244,7 +260,7 @@ function renderGroup(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderUser(name, data, parameters={}, options={}) {
- var html = `${data.username}`;
+ let html = `${data.username}`;
if (data.first_name && data.last_name) {
html += ` - ${data.first_name} ${data.last_name}`;
@@ -258,7 +274,7 @@ function renderUser(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderOwner(name, data, parameters={}, options={}) {
- var html = `${data.name}`;
+ let html = `${data.name}`;
switch (data.label) {
case 'user':
@@ -279,7 +295,7 @@ function renderOwner(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderPurchaseOrder(name, data, parameters={}, options={}) {
- var html = '';
+ let html = '';
if (data.supplier_detail) {
thumbnail = data.supplier_detail.thumbnail || data.supplier_detail.image;
@@ -309,7 +325,7 @@ function renderPurchaseOrder(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderSalesOrder(name, data, parameters={}, options={}) {
- var html = `${data.reference}`;
+ let html = `${data.reference}`;
var thumbnail = null;
@@ -334,7 +350,7 @@ function renderSalesOrder(name, data, parameters={}, options={}) {
// eslint-disable-next-line no-unused-vars
function renderSalesOrderShipment(name, data, parameters={}, options={}) {
- var html = `
+ let html = `
${data.order_detail.reference} - {% trans "Shipment" %} ${data.reference}
{% trans "Shipment ID" %}: ${data.pk}
@@ -353,7 +369,7 @@ function renderPartCategory(name, data, parameters={}, options={}) {
var level = '- '.repeat(data.level);
- var html = `${level}${data.pathstring}`;
+ let html = `${level}${data.pathstring}`;
if (data.description) {
html += ` - ${trim(data.description)}`;
@@ -373,7 +389,7 @@ function renderPartParameterTemplate(name, data, parameters={}, options={}) {
units = ` [${data.units}]`;
}
- var html = `${data.name}${units}`;
+ let html = `${data.name}${units}`;
return html;
}
@@ -394,7 +410,7 @@ function renderManufacturerPart(name, data, parameters={}, options={}) {
part_image = data.part_detail.thumbnail || data.part_detail.image;
}
- var html = '';
+ let html = '';
html += select2Thumbnail(manufacturer_image);
html += select2Thumbnail(part_image);
@@ -423,7 +439,7 @@ function renderSupplierPart(name, data, parameters={}, options={}) {
part_image = data.part_detail.thumbnail || data.part_detail.image;
}
- var html = '';
+ let html = '';
html += select2Thumbnail(supplier_image);
diff --git a/InvenTree/templates/js/translated/return_order.js b/InvenTree/templates/js/translated/return_order.js
index 1d61883357..03f3b8a4e2 100644
--- a/InvenTree/templates/js/translated/return_order.js
+++ b/InvenTree/templates/js/translated/return_order.js
@@ -50,6 +50,9 @@ function returnOrderFields(options={}) {
link: {
icon: 'fa-link',
},
+ contact: {
+ icon: 'fa-user',
+ },
responsible: {
icon: 'fa-user',
}
diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py
index fcbb8397a9..a6d0898f70 100644
--- a/InvenTree/users/models.py
+++ b/InvenTree/users/models.py
@@ -136,6 +136,7 @@ class RuleSet(models.Model):
'purchase_order': [
'company_company',
'company_companyattachment',
+ 'company_contact',
'company_manufacturerpart',
'company_manufacturerpartparameter',
'company_supplierpart',
@@ -149,6 +150,7 @@ class RuleSet(models.Model):
'sales_order': [
'company_company',
'company_companyattachment',
+ 'company_contact',
'order_salesorder',
'order_salesorderallocation',
'order_salesorderattachment',
@@ -160,6 +162,7 @@ class RuleSet(models.Model):
'return_order': [
'company_company',
'company_companyattachment',
+ 'company_contact',
'order_returnorder',
'order_returnorderlineitem',
'order_returnorderextraline',
@@ -181,7 +184,6 @@ class RuleSet(models.Model):
'common_webhookmessage',
'common_notificationentry',
'common_notificationmessage',
- 'company_contact',
'users_owner',
# Third-party tables