From 6ab03bd05a153fbd3b6e533bc7a49b07ab40e7bb Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 22 Apr 2020 21:26:38 +1000 Subject: [PATCH] Add form for creating a new StockItem allocation --- .../static/script/inventree/inventree.js | 8 +-- InvenTree/order/forms.py | 14 ++++ .../templates/order/sales_order_detail.html | 68 ++++++++++++++++--- InvenTree/order/urls.py | 9 +++ InvenTree/order/views.py | 65 ++++++++++++++++++ 5 files changed, 149 insertions(+), 15 deletions(-) diff --git a/InvenTree/InvenTree/static/script/inventree/inventree.js b/InvenTree/InvenTree/static/script/inventree/inventree.js index e2626e3c0a..6d29ca3704 100644 --- a/InvenTree/InvenTree/static/script/inventree/inventree.js +++ b/InvenTree/InvenTree/static/script/inventree/inventree.js @@ -78,16 +78,16 @@ function getImageUrlFromTransfer(transfer) { return url; } -function makeIconButton(icon, id, opts) { +function makeIconButton(icon, cls, pk, title) { // Construct an 'icon button' using the fontawesome set - var options = opts || {}; + var classes = `btn btn-default btn-glyph ${cls}`; - var title = options.title || ''; + var id = `${cls}-${pk}`; var html = ''; - html += ``; diff --git a/InvenTree/order/forms.py b/InvenTree/order/forms.py index 19580e7226..43a8d4a529 100644 --- a/InvenTree/order/forms.py +++ b/InvenTree/order/forms.py @@ -16,6 +16,7 @@ from InvenTree.fields import RoundingDecimalFormField from stock.models import StockLocation from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment +from .models import SalesOrderAllocation class IssuePurchaseOrderForm(HelperForm): @@ -144,3 +145,16 @@ class EditSalesOrderLineItemForm(HelperForm): 'reference', 'notes' ] + + +class EditSalesOrderAllocationForm(HelperForm): + + quantity = RoundingDecimalFormField(max_digits=10, decimal_places=5) + + class Meta: + model = SalesOrderAllocation + + fields = [ + 'line', + 'item', + 'quantity'] diff --git a/InvenTree/order/templates/order/sales_order_detail.html b/InvenTree/order/templates/order/sales_order_detail.html index 7870f77baf..92acf5edc0 100644 --- a/InvenTree/order/templates/order/sales_order_detail.html +++ b/InvenTree/order/templates/order/sales_order_detail.html @@ -136,32 +136,78 @@ $("#so-lines-table").inventreeTable({ field: 'buttons', formatter: function(value, row, index, field) { - var html = ''; + var html = `
`; var pk = row.pk; if (row.part) { var part = row.part_detail; - html = `
`; - - html += makeIconButton('fa-plus', `button-add-${pk}`); - if (part.purchaseable) { - html += makeIconButton('fa-shopping-cart', `button-buy-${pk}`); - } - - if (part.assembly) { - html += makeIconButton('fa-tools', `button-build-${pk}`); + html += makeIconButton('fa-shopping-cart', 'button-buy', pk, '{% trans "Buy parts" %}'); } - html += `
`; + if (part.assembly) { + html += makeIconButton('fa-tools', 'button-build', pk, '{% trans "Build parts" %}'); + } + + html += makeIconButton('fa-plus', 'button-add', pk, '{% trans "Allocate parts" %}'); + } + + html += makeIconButton('fa-edit', 'button-edit', pk, '{% trans "Edit line item" %}'); + html += `
`; return html; } }, ], +}); + +function reloadTable() { + $("#so-lines-table").bootstrapTable("refresh"); +} + +// Called when the table is loaded +$("#so-lines-table").on('load-success.bs.table', function() { + + var table = $(this); + + // Set up callbacks for the row buttons + table.find(".button-edit").click(function() { + + var pk = $(this).attr('pk'); + + launchModalForm(`/order/sales-order/line/${pk}/edit/`, { + success: reloadTable, + }); + console.log("clicked!"); }); + table.find(".button-add").click(function() { + console.log("add"); + + var pk = $(this).attr('pk'); + + launchModalForm(`/order/sales-order/allocation/new/`, { + reload: table, + data: { + line: pk, + }, + }); + + }); + + table.find(".button-build").click(function() { + console.log("build"); + + var pk = $(this).attr('pk'); + }); + + table.find(".button-buy").click(function() { + console.log("buy"); + }); + +}); + {% endblock %} \ No newline at end of file diff --git a/InvenTree/order/urls.py b/InvenTree/order/urls.py index e56d0c9312..fdc1aef3b5 100644 --- a/InvenTree/order/urls.py +++ b/InvenTree/order/urls.py @@ -61,8 +61,12 @@ purchase_order_urls = [ url(r'^.*$', views.PurchaseOrderIndex.as_view(), name='po-index'), ] + so_line_urls = [ url(r'^new/', views.SOLineItemCreate.as_view(), name='so-line-item-create'), + url(r'^(?P\d+)/', include([ + url(r'^edit/', views.SOLineItemEdit.as_view(), name='so-line-item-edit') + ])), ] sales_order_attachment_urls = [ @@ -88,6 +92,11 @@ sales_order_urls = [ url(r'^line/', include(so_line_urls)), + # URLs for sales order allocations + url(r'^allocation/', include([ + url(r'^new/', views.SalesOrderAllocationCreate.as_view(), name='so-allocation-create'), + ])), + url(r'^attachments/', include(sales_order_attachment_urls)), # Display detail view for a single SalesOrder diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index b25e5686f7..82b83b92cb 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -17,6 +17,7 @@ from decimal import Decimal, InvalidOperation from .models import PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderAttachment from .models import SalesOrder, SalesOrderLineItem, SalesOrderAttachment +from .models import SalesOrderAllocation from .admin import POLineItemResource from build.models import Build from company.models import Company, SupplierPart @@ -1114,6 +1115,22 @@ class SOLineItemCreate(AjaxCreateView): return initials +class SOLineItemEdit(AjaxUpdateView): + """ View for editing a SalesOrderLineItem """ + + model = SalesOrderLineItem + form_class = order_forms.EditSalesOrderLineItemForm + ajax_form_title = _('Edit Line Item') + + def get_form(self): + form = super().get_form() + + form.fields.pop('order') + form.fields.pop('part') + + return form + + class POLineItemEdit(AjaxUpdateView): """ View for editing a PurchaseOrderLineItem object in a modal form. """ @@ -1144,3 +1161,51 @@ class POLineItemDelete(AjaxDeleteView): return { 'danger': _('Deleted line item'), } + + +class SalesOrderAllocationCreate(AjaxCreateView): + """ View for creating a new SalesOrderAllocation """ + + model = SalesOrderAllocation + form_class = order_forms.EditSalesOrderAllocationForm + ajax_form_title = _('Allocate Stock to Order') + + def get_initial(self): + initials = super().get_initial().copy() + + line = self.request.GET.get('line', None) + + if line is not None: + initials['line'] = SalesOrderLineItem.objects.get(pk=line) + + return initials + + def get_form(self): + + form = super().get_form() + + line_id = form['line'].value() + + # If a line item has been specified, reduce the queryset for the stockitem accordingly + try: + line = SalesOrderLineItem.objects.get(pk=line_id) + + queryset = form.fields['item'].queryset + + # Ensure the part reference matches + queryset = queryset.filter(part=line.part) + + # Exclude StockItem which are already allocated to this order + allocated = [allocation.item.pk for allocation in line.allocations.all()] + + queryset = queryset.exclude(pk__in=allocated) + + form.fields['item'].queryset = queryset + + # Hide the 'line' field + form.fields['line'].widget = HiddenInput() + + except KeyError: # (ValueError, SalesOrderLineItem.DoesNotExist): + pass + + return form