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