From d5f3498238457f089aafbcf0d45be17a14f970c8 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Sat, 25 Apr 2020 13:15:45 +1000 Subject: [PATCH] Add a "sales_order" reference to the Build model - If a build order is made to fulfil a sales order - Add sales_order filtering to the Build API - Pass initial information through to the BuildCreate view --- InvenTree/build/api.py | 8 +--- InvenTree/build/forms.py | 1 + .../migrations/0012_build_sales_order.py | 20 ++++++++ InvenTree/build/models.py | 46 ++++++++++++------- InvenTree/build/serializers.py | 1 + .../build/templates/build/build_base.html | 17 ++++++- InvenTree/build/views.py | 12 ++--- .../templates/order/sales_order_detail.html | 20 ++++++++ 8 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 InvenTree/build/migrations/0012_build_sales_order.py diff --git a/InvenTree/build/api.py b/InvenTree/build/api.py index 5c059efaf8..ceddb10014 100644 --- a/InvenTree/build/api.py +++ b/InvenTree/build/api.py @@ -38,6 +38,8 @@ class BuildList(generics.ListCreateAPIView): ] filter_fields = [ + 'part', + 'sales_order', ] def get_queryset(self): @@ -48,12 +50,6 @@ class BuildList(generics.ListCreateAPIView): build_list = super().get_queryset() - # Filter by part - part = self.request.query_params.get('part', None) - - if part is not None: - build_list = build_list.filter(part=part) - # Filter by build status? status = self.request.query_params.get('status', None) diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index e060ff644d..011aab6a65 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -22,6 +22,7 @@ class EditBuildForm(HelperForm): fields = [ 'title', 'part', + 'sales_order', 'quantity', 'take_from', 'batch', diff --git a/InvenTree/build/migrations/0012_build_sales_order.py b/InvenTree/build/migrations/0012_build_sales_order.py new file mode 100644 index 0000000000..6b4a845a6e --- /dev/null +++ b/InvenTree/build/migrations/0012_build_sales_order.py @@ -0,0 +1,20 @@ +# Generated by Django 3.0.5 on 2020-04-24 22:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0029_auto_20200423_1042'), + ('build', '0011_auto_20200406_0123'), + ] + + operations = [ + migrations.AddField( + model_name='build', + name='sales_order', + field=models.ForeignKey(blank=True, help_text='SalesOrder to which this build is allocated', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='builds', to='order.SalesOrder'), + ), + ] diff --git a/InvenTree/build/models.py b/InvenTree/build/models.py index 532526ea60..06a382bbc1 100644 --- a/InvenTree/build/models.py +++ b/InvenTree/build/models.py @@ -33,6 +33,7 @@ class Build(models.Model): part: The part to be built (from component BOM items) title: Brief title describing the build (required) quantity: Number of units to be built + sales_order: References to a SalesOrder object for which this Build is required (e.g. the output of this build will be used to fulfil a sales order) take_from: Location to take stock from to make this build (if blank, can take from anywhere) status: Build status code batch: Batch code transferred to build parts (optional) @@ -51,24 +52,37 @@ class Build(models.Model): title = models.CharField( blank=False, max_length=100, - help_text=_('Brief description of the build')) + help_text=_('Brief description of the build') + ) - part = models.ForeignKey('part.Part', on_delete=models.CASCADE, - related_name='builds', - limit_choices_to={ - 'is_template': False, - 'assembly': True, - 'active': True, - 'virtual': False, - }, - help_text=_('Select part to build'), - ) + part = models.ForeignKey( + 'part.Part', + on_delete=models.CASCADE, + related_name='builds', + limit_choices_to={ + 'is_template': False, + 'assembly': True, + 'active': True, + 'virtual': False, + }, + help_text=_('Select part to build'), + ) + + sales_order = models.ForeignKey( + 'order.SalesOrder', + on_delete=models.SET_NULL, + related_name='builds', + null=True, blank=True, + help_text=_('SalesOrder to which this build is allocated') + ) - take_from = models.ForeignKey('stock.StockLocation', on_delete=models.SET_NULL, - related_name='sourcing_builds', - null=True, blank=True, - help_text=_('Select location to take stock from for this build (leave blank to take from any stock location)') - ) + take_from = models.ForeignKey( + 'stock.StockLocation', + on_delete=models.SET_NULL, + related_name='sourcing_builds', + null=True, blank=True, + help_text=_('Select location to take stock from for this build (leave blank to take from any stock location)') + ) quantity = models.PositiveIntegerField( default=1, diff --git a/InvenTree/build/serializers.py b/InvenTree/build/serializers.py index 19073d1e2d..bc90b2b152 100644 --- a/InvenTree/build/serializers.py +++ b/InvenTree/build/serializers.py @@ -39,6 +39,7 @@ class BuildSerializer(InvenTreeModelSerializer): 'completion_date', 'part', 'part_detail', + 'sales_order', 'quantity', 'status', 'status_text', diff --git a/InvenTree/build/templates/build/build_base.html b/InvenTree/build/templates/build/build_base.html index 0e0bf52849..af1852551d 100644 --- a/InvenTree/build/templates/build/build_base.html +++ b/InvenTree/build/templates/build/build_base.html @@ -8,6 +8,14 @@ InvenTree | {% trans "Build" %} - {{ build }} {% endblock %} +{% block pre_content %} +{% if build.sales_order %} +
+ {% trans "This build is allocated to Sales Order" %} {{ build.sales_order }} +
+{% endif %} +{% endblock %} + {% block thumbnail %} - Part + {% trans "Part" %} {{ build.part.full_name }} @@ -67,6 +75,13 @@ src="{% static 'img/blank_image.png' %}" {% trans "Status" %} {% build_status_label build.status %} + {% if build.sales_order %} + + + {% trans "Sales Order" %} + {{ build.sales_order }} + + {% endif %} {% trans "BOM Price" %} diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index b785bcb914..18f768ad2b 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -393,13 +393,13 @@ class BuildCreate(AjaxCreateView): initials = super(BuildCreate, self).get_initial().copy() - part_id = self.request.GET.get('part', None) + # User has provided a Part ID + initials['part'] = self.request.GET.get('part', None) - if part_id: - try: - initials['part'] = Part.objects.get(pk=part_id) - except Part.DoesNotExist: - pass + # User has provided a SalesOrder ID + initials['sales_order'] = self.request.GET.get('sales_order', None) + + initials['quantity'] = self.request.GET.get('quantity', 1) return initials diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index ab72fe5bfb..459f35ec8a 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -6,6 +6,9 @@ {% load static %} {% block details %} + +{% include "order/so_tabs.html" with tab='details' %} +

{% trans "Sales Order Items" %}

@@ -45,6 +48,7 @@ $("#so-lines-table").inventreeTable({ part_detail: true, allocations: true, }, + uniqueId: 'pk', url: "{% url 'api-so-line-list' %}", onPostBody: setupCallbacks, detailViewByClick: true, @@ -242,12 +246,28 @@ function setupCallbacks() { }); table.find(".button-build").click(function() { + var pk = $(this).attr('pk'); + // Extract the row data from the table! + var idx = $(this).closest('tr').attr('data-index'); + + var row = table.bootstrapTable('getData')[idx]; + + console.log('Row ' + idx + ' - ' + row.pk + ', ' + row.quantity); + + var quantity = 1; + + if (row.allocated < row.quantity) { + quantity = row.quantity - row.allocated; + } + launchModalForm(`/build/new/`, { follow: true, data: { part: pk, + sales_order: {{ order.id }}, + quantity: quantity, }, }); });