API changes

- Allow SupplierPart to be filtered by 'company' in addition to 'supplier' and 'manufacturer'
- Stock can now also be filtered by 'company'
This commit is contained in:
Oliver Walters 2020-04-13 18:50:59 +10:00
parent 635c4339e0
commit 1b1cd944be
10 changed files with 81 additions and 43 deletions

View File

@ -69,14 +69,6 @@ function loadCompanyTable(table, url, options={}) {
return ''; return '';
} }
}, },
{
field: 'part_count',
title: 'Parts',
sortable: true,
formatter: function(value, row, index, field) {
return renderLink(value, row.url + 'parts/');
}
},
], ],
}); });
} }

View File

@ -81,12 +81,19 @@ class SupplierPartList(generics.ListCreateAPIView):
queryset = SupplierPart.objects.all().prefetch_related( queryset = SupplierPart.objects.all().prefetch_related(
'part', 'part',
'part__category',
'part__stock_items',
'part__bom_items',
'part__builds',
'supplier', 'supplier',
'pricebreaks') 'manufacturer'
)
def get_queryset(self):
queryset = super().get_queryset()
# Filter by EITHER manufacturer or supplier
company = self.request.query_params.get('company', None)
if company is not None:
queryset = queryset.filter(Q(manufacturer=company) | Q(supplier=company))
def get_serializer(self, *args, **kwargs): def get_serializer(self, *args, **kwargs):

View File

@ -13,7 +13,7 @@ from decimal import Decimal
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.db import models from django.db import models
from django.db.models import Sum from django.db.models import Sum, Q
from django.apps import apps from django.apps import apps
from django.urls import reverse from django.urls import reverse
@ -140,26 +140,44 @@ class Company(models.Model):
return getBlankThumbnail() return getBlankThumbnail()
@property @property
def part_count(self): def manufactured_part_count(self):
""" The number of parts manufactured by this company """
return self.manufactured_parts.count()
@property
def has_manufactured_parts(self):
return self.manufactured_part_count > 0
@property
def supplied_part_count(self):
""" The number of parts supplied by this company """ """ The number of parts supplied by this company """
return self.supplied_parts.count()
@property
def has_supplied_parts(self):
""" Return True if this company supplies any parts """
return self.supplied_part_count > 0
@property
def parts(self):
""" Return SupplierPart objects which are supplied or manufactured by this company """
return SupplierPart.objects.filter(Q(supplier=self.id) | Q(manufacturer=self.id))
@property
def part_count(self):
""" The number of parts manufactured (or supplied) by this Company """
return self.parts.count() return self.parts.count()
@property
def has_parts(self):
""" Return True if this company supplies any parts """
return self.part_count > 0
@property @property
def stock_items(self): def stock_items(self):
""" Return a list of all stock items supplied by this company """ """ Return a list of all stock items supplied or manufactured by this company """
stock = apps.get_model('stock', 'StockItem') stock = apps.get_model('stock', 'StockItem')
return stock.objects.filter(supplier_part__supplier=self.id).all() return stock.objects.filter(Q(supplier_part__supplier=self.id) | Q(supplier_part__manufacturer=self.id)).all()
@property @property
def stock_count(self): def stock_count(self):
""" Return the number of stock items supplied by this company """ """ Return the number of stock items supplied or manufactured by this company """
stock = apps.get_model('stock', 'StockItem') return self.stock_items.count()
return stock.objects.filter(supplier_part__supplier=self.id).count()
def outstanding_purchase_orders(self): def outstanding_purchase_orders(self):
""" Return purchase orders which are 'outstanding' """ """ Return purchase orders which are 'outstanding' """
@ -255,7 +273,7 @@ class SupplierPart(models.Model):
) )
supplier = models.ForeignKey(Company, on_delete=models.CASCADE, supplier = models.ForeignKey(Company, on_delete=models.CASCADE,
related_name='parts', related_name='supplied_parts',
limit_choices_to={'is_supplier': True}, limit_choices_to={'is_supplier': True},
help_text=_('Select supplier'), help_text=_('Select supplier'),
) )

View File

@ -6,8 +6,8 @@ Are you sure you want to delete company '{{ company.name }}'?
<br> <br>
{% if company.part_count > 0 %} {% if company.supplied_part_count > 0 %}
<p>There are {{ company.part_count }} parts sourced from this company.<br> <p>There are {{ company.supplied_part_count }} parts sourced from this company.<br>
If this supplier is deleted, these supplier part entries will also be deleted.</p> If this supplier is deleted, these supplier part entries will also be deleted.</p>
<ul class='list-group'> <ul class='list-group'>
{% for part in company.parts.all %} {% for part in company.parts.all %}

View File

@ -12,15 +12,20 @@
<col width='25'> <col width='25'>
<col> <col>
<tr> <tr>
<td><span class='fas fa-user-tag'></span></td> <td><span class='fas fa-industry'></span></td>
<td>{% trans "Customer" %}</td> <td>{% trans "Manufacturer" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_customer %}</td> <td>{% include "yesnolabel.html" with value=company.is_manufacturer %}</td>
</tr> </tr>
<tr> <tr>
<td><span class='fas fa-industry'></span></td> <td><span class='fas fa-building'></span></td>
<td>{% trans "Supplier" %}</td> <td>{% trans "Supplier" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_supplier %}</td> <td>{% include 'yesnolabel.html' with value=company.is_supplier %}</td>
</tr> </tr>
<tr>
<td><span class='fas fa-user-tie'></span></td>
<td>{% trans "Customer" %}</td>
<td>{% include 'yesnolabel.html' with value=company.is_customer %}</td>
</tr>
</table> </table>
{% endblock %} {% endblock %}

View File

@ -19,8 +19,9 @@
loadStockTable($('#stock-table'), { loadStockTable($('#stock-table'), {
url: "{% url 'api-stock-list' %}", url: "{% url 'api-stock-list' %}",
params: { params: {
supplier: {{ company.id }}, company: {{ company.id }},
part_detail: true, part_detail: true,
supplier_detail: true,
location_detail: true, location_detail: true,
}, },
buttons: [ buttons: [

View File

@ -4,13 +4,15 @@
<li{% if tab == 'details' %} class='active'{% endif %}> <li{% if tab == 'details' %} class='active'{% endif %}>
<a href="{% url 'company-detail' company.id %}">{% trans "Details" %}</a> <a href="{% url 'company-detail' company.id %}">{% trans "Details" %}</a>
</li> </li>
{% if company.is_supplier %} {% if company.is_supplier or company.is_manufacturer %}
<li{% if tab == 'parts' %} class='active'{% endif %}> <li{% if tab == 'parts' %} class='active'{% endif %}>
<a href="{% url 'company-detail-parts' company.id %}">{% trans "Supplier Parts" %} <span class='badge'>{{ company.part_count }}</span></a> <a href="{% url 'company-detail-parts' company.id %}">{% trans "Parts" %} <span class='badge'>{{ company.part_count }}</span></a>
</li> </li>
<li{% if tab == 'stock' %} class='active'{% endif %}> <li{% if tab == 'stock' %} class='active'{% endif %}>
<a href="{% url 'company-detail-stock' company.id %}">{% trans "Stock" %} <span class='badge'>{{ company.stock_count }}</a> <a href="{% url 'company-detail-stock' company.id %}">{% trans "Stock" %} <span class='badge'>{{ company.stock_count }}</a>
</li> </li>
{% endif %}
{% if company.is_supplier %}
<li{% if tab == 'po' %} class='active'{% endif %}> <li{% if tab == 'po' %} class='active'{% endif %}>
<a href="{% url 'company-detail-purchase-orders' company.id %}">{% trans "Purchase Orders" %} <span class='badge'>{{ company.purchase_orders.count }}</span></a> <a href="{% url 'company-detail-purchase-orders' company.id %}">{% trans "Purchase Orders" %} <span class='badge'>{{ company.purchase_orders.count }}</span></a>
</li> </li>

View File

@ -56,13 +56,13 @@ class CompanySimpleTest(TestCase):
zerg = Company.objects.get(pk=3) zerg = Company.objects.get(pk=3)
self.assertTrue(acme.has_parts) self.assertTrue(acme.has_parts)
self.assertEqual(acme.part_count, 4) self.assertEqual(acme.supplied_part_count, 4)
self.assertTrue(appel.has_parts) self.assertTrue(appel.has_parts)
self.assertEqual(appel.part_count, 2) self.assertEqual(appel.supplied_part_count, 2)
self.assertTrue(zerg.has_parts) self.assertTrue(zerg.has_parts)
self.assertEqual(zerg.part_count, 1) self.assertEqual(zerg.supplied_part_count, 1)
def test_price_breaks(self): def test_price_breaks(self):

View File

@ -15,7 +15,7 @@ company_detail_urls = [
# url(r'orders/?', views.CompanyDetail.as_view(template_name='company/orders.html'), name='company-detail-orders'), # url(r'orders/?', views.CompanyDetail.as_view(template_name='company/orders.html'), name='company-detail-orders'),
url(r'parts/?', views.CompanyDetail.as_view(template_name='company/detail_part.html'), name='company-detail-parts'), url(r'parts/', views.CompanyDetail.as_view(template_name='company/detail_part.html'), name='company-detail-parts'),
url(r'stock/?', views.CompanyDetail.as_view(template_name='company/detail_stock.html'), name='company-detail-stock'), url(r'stock/?', views.CompanyDetail.as_view(template_name='company/detail_stock.html'), name='company-detail-stock'),
url(r'purchase-orders/?', views.CompanyDetail.as_view(template_name='company/detail_purchase_orders.html'), name='company-detail-purchase-orders'), url(r'purchase-orders/?', views.CompanyDetail.as_view(template_name='company/detail_purchase_orders.html'), name='company-detail-purchase-orders'),
url(r'notes/?', views.CompanyNotes.as_view(), name='company-notes'), url(r'notes/?', views.CompanyNotes.as_view(), name='company-notes'),

View File

@ -8,6 +8,7 @@ from django_filters import NumberFilter
from django.conf import settings from django.conf import settings
from django.conf.urls import url, include from django.conf.urls import url, include
from django.urls import reverse from django.urls import reverse
from django.db.models import Q
from .models import StockLocation, StockItem from .models import StockLocation, StockItem
from .models import StockItemTracking from .models import StockItemTracking
@ -494,11 +495,23 @@ class StockList(generics.ListCreateAPIView):
if supplier_part_id: if supplier_part_id:
stock_list = stock_list.filter(supplier_part=supplier_part_id) stock_list = stock_list.filter(supplier_part=supplier_part_id)
# Filter by supplier ID # Filter by company (either manufacturer or supplier)
supplier_id = self.request.query_params.get('supplier', None) company = self.request.query_params.get('company', None)
if supplier_id: if company is not None:
stock_list = stock_list.filter(supplier_part__supplier=supplier_id) stock_list = stock_list.filter(Q(supplier_part__supplier=company) | Q(supplier_part__manufacturer=company))
# Filter by supplier
supplier = self.request.query_params.get('supplier', None)
if supplier is not None:
stock_list = stock_list.filter(supplier_part__supplier=supplier)
# Filter by manufacturer
manufacturer = self.request.query_params.get('manufacturer', None)
if manufacturer is not None:
stock_list = stock_list.filter(supplier_part__manufacturer=manufacturer)
# Also ensure that we pre-fecth all the related items # Also ensure that we pre-fecth all the related items
stock_list = stock_list.prefetch_related( stock_list = stock_list.prefetch_related(