mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-30 20:55:42 +00:00 
			
		
		
		
	Merge branch 'sales-order-table-fixes' into sales-order-allocation-fixes
This commit is contained in:
		| @@ -549,7 +549,7 @@ class SOLineItemSerializer(InvenTreeModelSerializer): | |||||||
|  |  | ||||||
|     order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) |     order_detail = SalesOrderSerializer(source='order', many=False, read_only=True) | ||||||
|     part_detail = PartBriefSerializer(source='part', many=False, read_only=True) |     part_detail = PartBriefSerializer(source='part', many=False, read_only=True) | ||||||
|     allocations = SalesOrderAllocationSerializer(many=True, read_only=True) |     allocations = SalesOrderAllocationSerializer(many=True, read_only=True, location_detail=True) | ||||||
|  |  | ||||||
|     quantity = serializers.FloatField() |     quantity = serializers.FloatField() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -179,446 +179,17 @@ $("#new-so-line").click(function() { | |||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| {% if order.status == SalesOrderStatus.PENDING %} |     loadSalesOrderLineItemTable( | ||||||
| function showAllocationSubTable(index, row, element) { |         '#so-lines-table', | ||||||
|     // Construct a table showing stock items which have been allocated against this line item |  | ||||||
|  |  | ||||||
|     var html = `<div class='sub-table'><table class='table table-striped table-condensed' id='allocation-table-${row.pk}'></table></div>`; |  | ||||||
|  |  | ||||||
|     element.html(html); |  | ||||||
|  |  | ||||||
|     var lineItem = row; |  | ||||||
|  |  | ||||||
|     var table = $(`#allocation-table-${row.pk}`); |  | ||||||
|  |  | ||||||
|     table.bootstrapTable({ |  | ||||||
|         data: row.allocations, |  | ||||||
|         showHeader: false, |  | ||||||
|         columns: [ |  | ||||||
|         { |         { | ||||||
|             width: '50%', |             order: {{ order.pk }}, | ||||||
|             field: 'allocated', |             status: {{ order.status }}, | ||||||
|             title: '{% trans "Quantity" %}', |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|                 var text = ''; |  | ||||||
|  |  | ||||||
|                 if (row.serial != null && row.quantity == 1) { |  | ||||||
|                     text = `{% trans "Serial Number" %}: ${row.serial}`; |  | ||||||
|                 } else { |  | ||||||
|                     text = `{% trans "Quantity" %}: ${row.quantity}`; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return renderLink(text, `/stock/item/${row.item}/`); |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             field: 'location', |  | ||||||
|             title: 'Location', |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|                 return renderLink(row.location_path, `/stock/location/${row.location}/`); |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             field: 'po' |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             field: 'buttons', |  | ||||||
|             title: '{% trans "Actions" %}', |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|  |  | ||||||
|                 var html = "<div class='btn-group float-right' role='group'>"; |  | ||||||
|                 var pk = row.pk; |  | ||||||
|  |  | ||||||
|                 {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|                 html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}'); |  | ||||||
|                 html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}'); |  | ||||||
|                 {% endif %} |  | ||||||
|  |  | ||||||
|                 html += "</div>"; |  | ||||||
|  |  | ||||||
|                 return html; |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     table.find(".button-allocation-edit").click(function() { |  | ||||||
|  |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         launchModalForm(`/order/sales-order/allocation/${pk}/edit/`, { |  | ||||||
|             success: reloadTable, |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     table.find(".button-allocation-delete").click(function() { |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         launchModalForm(`/order/sales-order/allocation/${pk}/delete/`, { |  | ||||||
|             success: reloadTable,  |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
| {% endif %} |  | ||||||
|  |  | ||||||
| function showFulfilledSubTable(index, row, element) { |  | ||||||
|     // Construct a table showing stock items which have been fulfilled against this line item |  | ||||||
|  |  | ||||||
|     var id = `fulfilled-table-${row.pk}`; |  | ||||||
|     var html = `<div class='sub-table'><table class='table table-striped table-condensed' id='${id}'></table></div>`; |  | ||||||
|  |  | ||||||
|     element.html(html); |  | ||||||
|  |  | ||||||
|     var lineItem = row; |  | ||||||
|  |  | ||||||
|     $(`#${id}`).bootstrapTable({ |  | ||||||
|         url: "{% url 'api-stock-list' %}", |  | ||||||
|         queryParams: { |  | ||||||
|             part: row.part, |  | ||||||
|             sales_order: {{ order.id }}, |  | ||||||
|         }, |  | ||||||
|         showHeader: false, |  | ||||||
|         columns: [ |  | ||||||
|             { |  | ||||||
|                 field: 'pk', |  | ||||||
|                 visible: false, |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|                 field: 'stock', |  | ||||||
|                 formatter: function(value, row) { |  | ||||||
|                     var text = ''; |  | ||||||
|                     if (row.serial && row.quantity == 1) { |  | ||||||
|                         text = `{% trans "Serial Number" %}: ${row.serial}`; |  | ||||||
|                     } else { |  | ||||||
|                         text = `{% trans "Quantity" %}: ${row.quantity}`; |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     return renderLink(text, `/stock/item/${row.pk}/`); |  | ||||||
|                 }, |  | ||||||
|             }, |  | ||||||
|             { |  | ||||||
|                 field: 'po' |  | ||||||
|             }, |  | ||||||
|         ], |  | ||||||
|     }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| $("#so-lines-table").inventreeTable({ |  | ||||||
|     formatNoMatches: function() { return "{% trans 'No matching line items' %}"; }, |  | ||||||
|     queryParams: { |  | ||||||
|         order: {{ order.id }}, |  | ||||||
|         part_detail: true, |  | ||||||
|         allocations: true, |  | ||||||
|     }, |  | ||||||
|     sidePagination: 'server', |  | ||||||
|     uniqueId: 'pk', |  | ||||||
|     url: "{% url 'api-so-line-list' %}", |  | ||||||
|     onPostBody: setupCallbacks, |  | ||||||
|     {% if order.status == SalesOrderStatus.PENDING or order.status == SalesOrderStatus.SHIPPED %} |  | ||||||
|     detailViewByClick: true, |  | ||||||
|     detailView: true, |  | ||||||
|     detailFilter: function(index, row) { |  | ||||||
|         {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|         return row.allocated > 0; |  | ||||||
|         {% else %} |  | ||||||
|         return row.fulfilled > 0; |  | ||||||
|         {% endif %} |  | ||||||
|     }, |  | ||||||
|     {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|     detailFormatter: showAllocationSubTable, |  | ||||||
|     {% else %} |  | ||||||
|     detailFormatter: showFulfilledSubTable, |  | ||||||
|     {% endif %} |  | ||||||
|     {% endif %} |  | ||||||
|     showFooter: true, |  | ||||||
|     columns: [ |  | ||||||
|         { |  | ||||||
|             field: 'pk', |  | ||||||
|             title: '{% trans "ID" %}', |  | ||||||
|             visible: false, |  | ||||||
|             switchable: false, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             sortable: true, |  | ||||||
|             sortName: 'part__name', |  | ||||||
|             field: 'part', |  | ||||||
|             title: '{% trans "Part" %}', |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|                 if (row.part) { |  | ||||||
|                     return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`); |  | ||||||
|                 } else { |  | ||||||
|                     return '-'; |  | ||||||
|                 } |  | ||||||
|             }, |  | ||||||
|             footerFormatter:  function() { |  | ||||||
|                 return '{% trans "Total" %}' |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             sortable: true, |  | ||||||
|             field: 'reference', |  | ||||||
|             title: '{% trans "Reference" %}' |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             sortable: true, |  | ||||||
|             field: 'quantity', |  | ||||||
|             title: '{% trans "Quantity" %}', |  | ||||||
|             footerFormatter: function(data) { |  | ||||||
|                 return data.map(function (row) { |  | ||||||
|                   return +row['quantity'] |  | ||||||
|                 }).reduce(function (sum, i) { |  | ||||||
|                   return sum + i |  | ||||||
|                 }, 0) |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             sortable: true, |  | ||||||
|             field: 'sale_price', |  | ||||||
|             title: '{% trans "Unit Price" %}', |  | ||||||
|             formatter: function(value, row) { |  | ||||||
|                 return row.sale_price_string || row.sale_price; |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             sortable: true, |  | ||||||
|             title: '{% trans "Total price" %}', |  | ||||||
|             formatter: function(value, row) { |  | ||||||
|                 var total = row.sale_price * row.quantity; |  | ||||||
|                 var formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: row.sale_price_currency}); |  | ||||||
|                 return formatter.format(total) |  | ||||||
|             }, |  | ||||||
|             footerFormatter: function(data) { |  | ||||||
|                 var total = data.map(function (row) { |  | ||||||
|                   return +row['sale_price']*row['quantity'] |  | ||||||
|                 }).reduce(function (sum, i) { |  | ||||||
|                   return sum + i |  | ||||||
|                 }, 0) |  | ||||||
|                 var currency = (data.slice(-1)[0] && data.slice(-1)[0].sale_price_currency)  || 'USD'; |  | ||||||
|                 var formatter = new Intl.NumberFormat('en-US', {style: 'currency', currency: currency}); |  | ||||||
|                 return formatter.format(total) |  | ||||||
|               } |  | ||||||
|         }, |  | ||||||
|  |  | ||||||
|         { |  | ||||||
|             field: 'allocated', |  | ||||||
|             {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|             title: '{% trans "Allocated" %}', |  | ||||||
|             {% else %} |  | ||||||
|             title: '{% trans "Fulfilled" %}', |  | ||||||
|             {% endif %} |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|                 {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|                 var quantity = row.allocated; |  | ||||||
|                 {% else %} |  | ||||||
|                 var quantity = row.fulfilled; |  | ||||||
|                 {% endif %} |  | ||||||
|                 return makeProgressBar(quantity, row.quantity, { |  | ||||||
|                     id: `order-line-progress-${row.pk}`, |  | ||||||
|                 }); |  | ||||||
|             }, |  | ||||||
|             sorter: function(valA, valB, rowA, rowB) { |  | ||||||
|                 {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|                 var A = rowA.allocated; |  | ||||||
|                 var B = rowB.allocated; |  | ||||||
|                 {% else %} |  | ||||||
|                 var A = rowA.fulfilled; |  | ||||||
|                 var B = rowB.fulfilled; |  | ||||||
|                 {% endif %} |  | ||||||
|  |  | ||||||
|                 if (A == 0 && B == 0) { |  | ||||||
|                     return (rowA.quantity > rowB.quantity) ? 1 : -1; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 var progressA = parseFloat(A) / rowA.quantity; |  | ||||||
|                 var progressB = parseFloat(B) / rowB.quantity; |  | ||||||
|  |  | ||||||
|                 return (progressA < progressB) ? 1 : -1; |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             field: 'notes', |  | ||||||
|             title: '{% trans "Notes" %}', |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             field: 'po', |  | ||||||
|             title: '{% trans "PO" %}', |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|                 var po_name = ""; |  | ||||||
|                 if (row.allocated) { |  | ||||||
|                     row.allocations.forEach(function(allocation) { |  | ||||||
|                         if (allocation.po != po_name) { |  | ||||||
|                             if (po_name) { |  | ||||||
|                                 po_name = "-"; |  | ||||||
|                             } else { |  | ||||||
|                                 po_name = allocation.po |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     }) |  | ||||||
|                 } |  | ||||||
|                 return `<div>` + po_name + `</div>`; |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         {% if order.status == SalesOrderStatus.PENDING %} |  | ||||||
|         { |  | ||||||
|             field: 'buttons', |  | ||||||
|             formatter: function(value, row, index, field) { |  | ||||||
|  |  | ||||||
|                 var html = `<div class='btn-group float-right' role='group'>`; |  | ||||||
|  |  | ||||||
|                 var pk = row.pk; |  | ||||||
|  |  | ||||||
|                 if (row.part) { |  | ||||||
|                     var part = row.part_detail; |  | ||||||
|  |  | ||||||
|                     if (part.trackable) { |  | ||||||
|                         html += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate serial numbers" %}'); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}'); |  | ||||||
|  |  | ||||||
|                     if (part.purchaseable) { |  | ||||||
|                         html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Purchase stock" %}'); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if (part.assembly) { |  | ||||||
|                         html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build stock" %}'); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}'); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}'); |  | ||||||
|                 html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line item " %}'); |  | ||||||
|  |  | ||||||
|                 html += `</div>`; |  | ||||||
|  |  | ||||||
|                 return html; |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|         {% endif %} |  | ||||||
|     ], |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| function setupCallbacks() { |  | ||||||
|  |  | ||||||
|     var table = $("#so-lines-table"); |  | ||||||
|  |  | ||||||
|     // Set up callbacks for the row buttons |  | ||||||
|     table.find(".button-edit").click(function() { |  | ||||||
|  |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         constructForm(`/api/order/so-line/${pk}/`, { |  | ||||||
|             fields: { |  | ||||||
|                 quantity: {}, |  | ||||||
|                 reference: {}, |  | ||||||
|                 sale_price: {}, |  | ||||||
|                 sale_price_currency: {}, |  | ||||||
|                 notes: {}, |  | ||||||
|             }, |  | ||||||
|             title: '{% trans "Edit Line Item" %}', |  | ||||||
|             onSuccess: reloadTable, |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     table.find(".button-delete").click(function() { |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         constructForm(`/api/order/so-line/${pk}/`, { |  | ||||||
|             method: 'DELETE', |  | ||||||
|             title: '{% trans "Delete Line Item" %}', |  | ||||||
|             onSuccess: reloadTable, |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     table.find(".button-add-by-sn").click(function() { |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         inventreeGet(`/api/order/so-line/${pk}/`, {}, |  | ||||||
|             { |  | ||||||
|                 success: function(response) { |  | ||||||
|                     launchModalForm('{% url "so-assign-serials" %}', { |  | ||||||
|                         success: reloadTable, |  | ||||||
|                         data: { |  | ||||||
|                             line: pk, |  | ||||||
|                             part: response.part,  |  | ||||||
|                         } |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|         } |         } | ||||||
|     ); |     ); | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     table.find(".button-add").click(function() { |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         launchModalForm(`/order/sales-order/allocation/new/`, { |  | ||||||
|             success: reloadTable, |  | ||||||
|             data: { |  | ||||||
|                 line: pk, |  | ||||||
|             }, |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     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]; |  | ||||||
|  |  | ||||||
|         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, |  | ||||||
|             }, |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     table.find(".button-buy").click(function() { |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|  |  | ||||||
|         launchModalForm("{% url 'order-parts' %}", { |  | ||||||
|             data: { |  | ||||||
|                 parts: [pk], |  | ||||||
|             }, |  | ||||||
|         }); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     $(".button-price").click(function() { |  | ||||||
|         var pk = $(this).attr('pk'); |  | ||||||
|         var idx = $(this).closest('tr').attr('data-index'); |  | ||||||
|         var row = table.bootstrapTable('getData')[idx]; |  | ||||||
|  |  | ||||||
|         launchModalForm( |  | ||||||
|             "{% url 'line-pricing' %}", |  | ||||||
|             { |  | ||||||
|                 submit_text: '{% trans "Calculate price" %}', |  | ||||||
|                 data: { |  | ||||||
|                     line_item: pk, |  | ||||||
|                     quantity: row.quantity, |  | ||||||
|                 }, |  | ||||||
|                 buttons: [{name: 'update_price', |  | ||||||
|                     title: '{% trans "Update Unit Price" %}'},], |  | ||||||
|                 success: reloadTable, |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     attachNavCallbacks({ |     attachNavCallbacks({ | ||||||
|         name: 'sales-order', |         name: 'sales-order', | ||||||
|         default: 'order-items' |         default: 'order-items' | ||||||
|     }); |     }); | ||||||
| } |  | ||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
| @@ -42,6 +42,9 @@ function buildFormFields() { | |||||||
|                 part_detail: true, |                 part_detail: true, | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|  |         sales_order: { | ||||||
|  |             hidden: true, | ||||||
|  |         }, | ||||||
|         batch: {}, |         batch: {}, | ||||||
|         target_date: {}, |         target_date: {}, | ||||||
|         take_from: {}, |         take_from: {}, | ||||||
| @@ -76,23 +79,32 @@ function newBuildOrder(options={}) { | |||||||
|  |  | ||||||
|     var fields = buildFormFields(); |     var fields = buildFormFields(); | ||||||
|  |  | ||||||
|  |     // Specify the target part | ||||||
|     if (options.part) { |     if (options.part) { | ||||||
|         fields.part.value = options.part; |         fields.part.value = options.part; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Specify the desired quantity | ||||||
|     if (options.quantity) { |     if (options.quantity) { | ||||||
|         fields.quantity.value = options.quantity; |         fields.quantity.value = options.quantity; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Specify the parent build order | ||||||
|     if (options.parent) { |     if (options.parent) { | ||||||
|         fields.parent.value = options.parent; |         fields.parent.value = options.parent; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Specify a parent sales order | ||||||
|  |     if (options.sales_order) { | ||||||
|  |         fields.sales_order.value = options.sales_order; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     constructForm(`/api/build/`, { |     constructForm(`/api/build/`, { | ||||||
|         fields: fields, |         fields: fields, | ||||||
|         follow: true, |         follow: true, | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         title: '{% trans "Create Build Order" %}' |         title: '{% trans "Create Build Order" %}', | ||||||
|  |         onSuccess: options.onSuccess, | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ | |||||||
|     loadPurchaseOrderLineItemTable, |     loadPurchaseOrderLineItemTable, | ||||||
|     loadPurchaseOrderTable, |     loadPurchaseOrderTable, | ||||||
|     loadSalesOrderAllocationTable, |     loadSalesOrderAllocationTable, | ||||||
|  |     loadSalesOrderLineItemTable, | ||||||
|     loadSalesOrderTable, |     loadSalesOrderTable, | ||||||
|     newPurchaseOrderFromOrderWizard, |     newPurchaseOrderFromOrderWizard, | ||||||
|     newSupplierPartFromOrderWizard, |     newSupplierPartFromOrderWizard, | ||||||
| @@ -1126,3 +1127,574 @@ function loadSalesOrderAllocationTable(table, options={}) { | |||||||
|         ] |         ] | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Display an "allocations" sub table, showing stock items allocated againt a sales order | ||||||
|  |  * @param {*} index  | ||||||
|  |  * @param {*} row  | ||||||
|  |  * @param {*} element  | ||||||
|  |  */ | ||||||
|  | function showAllocationSubTable(index, row, element, options) { | ||||||
|  |      | ||||||
|  |     // Construct a sub-table element | ||||||
|  |     var html = ` | ||||||
|  |     <div class='sub-table'> | ||||||
|  |         <table class='table table-striped table-condensed' id='allocation-table-${row.pk}'> | ||||||
|  |         </table> | ||||||
|  |     </div>`; | ||||||
|  |  | ||||||
|  |     element.html(html); | ||||||
|  |  | ||||||
|  |     var table = $(`#allocation-table-${row.pk}`); | ||||||
|  |  | ||||||
|  |     // Is the parent SalesOrder pending? | ||||||
|  |     var pending = options.status == {{ SalesOrderStatus.PENDING }}; | ||||||
|  |  | ||||||
|  |     // Function to reload the allocation table | ||||||
|  |     function reloadTable() { | ||||||
|  |         table.bootstrapTable('refresh'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function setupCallbacks() { | ||||||
|  |         // Add callbacks for 'edit' buttons | ||||||
|  |         table.find('.button-allocation-edit').click(function() { | ||||||
|  |  | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |  | ||||||
|  |             // TODO: Migrate to API forms | ||||||
|  |             launchModalForm(`/order/sales-order/allocation/${pk}/edit/`, { | ||||||
|  |                 success: reloadTable, | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Add callbacks for 'delete' buttons | ||||||
|  |         table.find('.button-allocation-delete').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |              | ||||||
|  |             // TODO: Migrate to API forms | ||||||
|  |             launchModalForm(`/order/sales-order/allocation/${pk}/delete/`, { | ||||||
|  |                 success: reloadTable,  | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     table.bootstrapTable({ | ||||||
|  |         onPostBody: setupCallbacks, | ||||||
|  |         data: row.allocations, | ||||||
|  |         showHeader: false, | ||||||
|  |         columns: [ | ||||||
|  |             { | ||||||
|  |                 field: 'allocated', | ||||||
|  |                 title: '{% trans "Quantity" %}', | ||||||
|  |                 formatter: function(value, row, index, field) { | ||||||
|  |                     var text = ''; | ||||||
|  |  | ||||||
|  |                     if (row.serial != null && row.quantity == 1) { | ||||||
|  |                         text = `{% trans "Serial Number" %}: ${row.serial}`; | ||||||
|  |                     } else { | ||||||
|  |                         text = `{% trans "Quantity" %}: ${row.quantity}`; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     return renderLink(text, `/stock/item/${row.item}/`); | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 field: 'location', | ||||||
|  |                 title: '{% trans "Location" %}', | ||||||
|  |                 formatter: function(value, row, index, field) { | ||||||
|  |  | ||||||
|  |                     // Location specified | ||||||
|  |                     if (row.location) { | ||||||
|  |                         return renderLink( | ||||||
|  |                             row.location_detail.pathstring || '{% trans "Location" %}', | ||||||
|  |                             `/stock/location/${row.location}/` | ||||||
|  |                         ); | ||||||
|  |                     } else { | ||||||
|  |                         return `<i>{% trans "Stock location not specified" %}`; | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             // TODO: ?? What is 'po' field all about? | ||||||
|  |             /* | ||||||
|  |             { | ||||||
|  |                 field: 'po' | ||||||
|  |             }, | ||||||
|  |             */ | ||||||
|  |             { | ||||||
|  |                 field: 'buttons', | ||||||
|  |                 title: '{% trans "Actions" %}', | ||||||
|  |                 formatter: function(value, row, index, field) { | ||||||
|  |  | ||||||
|  |                     var html = `<div class='btn-group float-right' role='group'>`; | ||||||
|  |                     var pk = row.pk; | ||||||
|  |  | ||||||
|  |                     if (pending) { | ||||||
|  |                         html += makeIconButton('fa-edit icon-blue', 'button-allocation-edit', pk, '{% trans "Edit stock allocation" %}'); | ||||||
|  |                         html += makeIconButton('fa-trash-alt icon-red', 'button-allocation-delete', pk, '{% trans "Delete stock allocation" %}'); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     html += "</div>"; | ||||||
|  |  | ||||||
|  |                     return html; | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |         ], | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Display a "fulfilled" sub table, showing stock items fulfilled against a purchase order | ||||||
|  |  */ | ||||||
|  | function showFulfilledSubTable(index, row, element, options) { | ||||||
|  |     // Construct a table showing stock items which have been fulfilled against this line item | ||||||
|  |  | ||||||
|  |     if (!options.order) { | ||||||
|  |         return 'ERROR: Order ID not supplied'; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     var id = `fulfilled-table-${row.pk}`; | ||||||
|  |      | ||||||
|  |     var html = ` | ||||||
|  |     <div class='sub-table'> | ||||||
|  |         <table class='table table-striped table-condensed' id='${id}'> | ||||||
|  |         </table> | ||||||
|  |     </div>`; | ||||||
|  |  | ||||||
|  |     element.html(html); | ||||||
|  |  | ||||||
|  |     $(`#${id}`).bootstrapTable({ | ||||||
|  |         url: '{% url "api-stock-list" %}', | ||||||
|  |         queryParams: { | ||||||
|  |             part: row.part, | ||||||
|  |             sales_order: options.order, | ||||||
|  |         }, | ||||||
|  |         showHeader: false, | ||||||
|  |         columns: [ | ||||||
|  |             { | ||||||
|  |                 field: 'pk', | ||||||
|  |                 visible: false, | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 field: 'stock', | ||||||
|  |                 formatter: function(value, row) { | ||||||
|  |                     var text = ''; | ||||||
|  |                     if (row.serial && row.quantity == 1) { | ||||||
|  |                         text = `{% trans "Serial Number" %}: ${row.serial}`; | ||||||
|  |                     } else { | ||||||
|  |                         text = `{% trans "Quantity" %}: ${row.quantity}`; | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     return renderLink(text, `/stock/item/${row.pk}/`); | ||||||
|  |                 }, | ||||||
|  |             }, | ||||||
|  |             /* | ||||||
|  |             { | ||||||
|  |                 field: 'po' | ||||||
|  |             }, | ||||||
|  |             */ | ||||||
|  |         ], | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Load a table displaying line items for a particular SalesOrder | ||||||
|  |  *  | ||||||
|  |  * @param {String} table : HTML ID tag e.g. '#table' | ||||||
|  |  * @param {Object} options : object which contains: | ||||||
|  |  *      - order {integer} : pk of the SalesOrder | ||||||
|  |  *      - status: {integer} : status code for the order | ||||||
|  |  */ | ||||||
|  | function loadSalesOrderLineItemTable(table, options={}) { | ||||||
|  |  | ||||||
|  |     options.params = options.params || {}; | ||||||
|  |  | ||||||
|  |     if (!options.order) { | ||||||
|  |         console.log('ERROR: function called without order ID'); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!options.status) { | ||||||
|  |         console.log('ERROR: function called without order status'); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     options.params.order = options.order; | ||||||
|  |     options.params.part_detail = true; | ||||||
|  |     options.params.allocations = true; | ||||||
|  |      | ||||||
|  |     var filters = loadTableFilters('salesorderlineitem'); | ||||||
|  |  | ||||||
|  |     for (var key in options.params) { | ||||||
|  |         filters[key] = options.params[key]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     options.url = options.url || '{% url "api-so-line-list" %}'; | ||||||
|  |  | ||||||
|  |     var filter_target = options.filter_target || '#filter-list-sales-order-lines'; | ||||||
|  |  | ||||||
|  |     setupFilterList('salesorderlineitems', $(table), filter_target); | ||||||
|  |  | ||||||
|  |     // Is the order pending? | ||||||
|  |     var pending = options.status == {{ SalesOrderStatus.PENDING }}; | ||||||
|  |  | ||||||
|  |     // Has the order shipped? | ||||||
|  |     var shipped = options.status == {{ SalesOrderStatus.SHIPPED }}; | ||||||
|  |  | ||||||
|  |     // Show detail view if the PurchaseOrder is PENDING or SHIPPED | ||||||
|  |     var show_detail = pending || shipped; | ||||||
|  |  | ||||||
|  |     // Table columns to display | ||||||
|  |     var columns = [ | ||||||
|  |         /* | ||||||
|  |         { | ||||||
|  |             checkbox: true, | ||||||
|  |             visible: true, | ||||||
|  |             switchable: false, | ||||||
|  |         }, | ||||||
|  |         */ | ||||||
|  |         { | ||||||
|  |             sortable: true, | ||||||
|  |             sortName: 'part__name', | ||||||
|  |             field: 'part', | ||||||
|  |             title: '{% trans "Part" %}', | ||||||
|  |             switchable: false, | ||||||
|  |             formatter: function(value, row, index, field) { | ||||||
|  |                 if (row.part) { | ||||||
|  |                     return imageHoverIcon(row.part_detail.thumbnail) + renderLink(row.part_detail.full_name, `/part/${value}/`); | ||||||
|  |                 } else { | ||||||
|  |                     return '-'; | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             footerFormatter:  function() { | ||||||
|  |                 return '{% trans "Total" %}' | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             sortable: true, | ||||||
|  |             field: 'reference', | ||||||
|  |             title: '{% trans "Reference" %}', | ||||||
|  |             switchable: false, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             sortable: true, | ||||||
|  |             field: 'quantity', | ||||||
|  |             title: '{% trans "Quantity" %}', | ||||||
|  |             footerFormatter: function(data) { | ||||||
|  |                 return data.map(function (row) { | ||||||
|  |                   return +row['quantity'] | ||||||
|  |                 }).reduce(function (sum, i) { | ||||||
|  |                   return sum + i | ||||||
|  |                 }, 0) | ||||||
|  |             }, | ||||||
|  |             switchable: false, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             sortable: true, | ||||||
|  |             field: 'sale_price', | ||||||
|  |             title: '{% trans "Unit Price" %}', | ||||||
|  |             formatter: function(value, row) { | ||||||
|  |                 return row.sale_price_string || row.sale_price; | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             sortable: true, | ||||||
|  |             title: '{% trans "Total price" %}', | ||||||
|  |             formatter: function(value, row) { | ||||||
|  |                 var total = row.sale_price * row.quantity; | ||||||
|  |                 var formatter = new Intl.NumberFormat( | ||||||
|  |                     'en-US', | ||||||
|  |                     { | ||||||
|  |                         style: 'currency', | ||||||
|  |                         currency: row.sale_price_currency | ||||||
|  |                     } | ||||||
|  |                 ); | ||||||
|  |                 return formatter.format(total); | ||||||
|  |             }, | ||||||
|  |             footerFormatter: function(data) { | ||||||
|  |                 var total = data.map(function (row) { | ||||||
|  |                     return +row['sale_price'] * row['quantity']; | ||||||
|  |                 }).reduce(function (sum, i) { | ||||||
|  |                     return sum + i; | ||||||
|  |                 }, 0); | ||||||
|  |  | ||||||
|  |                 var currency = (data.slice(-1)[0] && data.slice(-1)[0].sale_price_currency) || 'USD'; | ||||||
|  |                  | ||||||
|  |                 var formatter = new Intl.NumberFormat( | ||||||
|  |                     'en-US', | ||||||
|  |                     { | ||||||
|  |                         style: 'currency',  | ||||||
|  |                         currency: currency | ||||||
|  |                     } | ||||||
|  |                 ); | ||||||
|  |                  | ||||||
|  |                 return formatter.format(total); | ||||||
|  |               } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'stock', | ||||||
|  |             title: '{% trans "In Stock" %}', | ||||||
|  |             formatter: function(value, row) { | ||||||
|  |                 return row.part_detail.stock; | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'allocated', | ||||||
|  |             title: pending ? '{% trans "Allocated" %}' : '{% trans "Fulfilled" %}', | ||||||
|  |             switchable: false, | ||||||
|  |             formatter: function(value, row, index, field) { | ||||||
|  |  | ||||||
|  |                 var quantity = pending ? row.allocated : row.fulfilled; | ||||||
|  |                 return makeProgressBar(quantity, row.quantity, { | ||||||
|  |                     id: `order-line-progress-${row.pk}`, | ||||||
|  |                 }); | ||||||
|  |             }, | ||||||
|  |             sorter: function(valA, valB, rowA, rowB) { | ||||||
|  |  | ||||||
|  |                 var A = pending ? rowA.allocated : rowA.fulfilled; | ||||||
|  |                 var B = pending ? rowB.allocated : rowB.fulfilled; | ||||||
|  |  | ||||||
|  |                 if (A == 0 && B == 0) { | ||||||
|  |                     return (rowA.quantity > rowB.quantity) ? 1 : -1; | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 var progressA = parseFloat(A) / rowA.quantity; | ||||||
|  |                 var progressB = parseFloat(B) / rowB.quantity; | ||||||
|  |  | ||||||
|  |                 return (progressA < progressB) ? 1 : -1; | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             field: 'notes', | ||||||
|  |             title: '{% trans "Notes" %}', | ||||||
|  |         }, | ||||||
|  |         // TODO: Re-introduce the "PO" field, once it is fixed | ||||||
|  |         /* | ||||||
|  |         { | ||||||
|  |             field: 'po', | ||||||
|  |             title: '{% trans "PO" %}', | ||||||
|  |             formatter: function(value, row, index, field) { | ||||||
|  |                 var po_name = ""; | ||||||
|  |                 if (row.allocated) { | ||||||
|  |                     row.allocations.forEach(function(allocation) { | ||||||
|  |                         if (allocation.po != po_name) { | ||||||
|  |                             if (po_name) { | ||||||
|  |                                 po_name = "-"; | ||||||
|  |                             } else { | ||||||
|  |                                 po_name = allocation.po | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|  |                 return `<div>` + po_name + `</div>`; | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         */ | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     if (pending) { | ||||||
|  |         columns.push({ | ||||||
|  |             field: 'buttons', | ||||||
|  |             formatter: function(value, row, index, field) { | ||||||
|  |  | ||||||
|  |                 var html = `<div class='btn-group float-right' role='group'>`; | ||||||
|  |  | ||||||
|  |                 var pk = row.pk; | ||||||
|  |  | ||||||
|  |                 if (row.part) { | ||||||
|  |                     var part = row.part_detail; | ||||||
|  |  | ||||||
|  |                     if (part.trackable) { | ||||||
|  |                         html += makeIconButton('fa-hashtag icon-green', 'button-add-by-sn', pk, '{% trans "Allocate serial numbers" %}'); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     html += makeIconButton('fa-sign-in-alt icon-green', 'button-add', pk, '{% trans "Allocate stock" %}'); | ||||||
|  |  | ||||||
|  |                     if (part.purchaseable) { | ||||||
|  |                         html += makeIconButton('fa-shopping-cart', 'button-buy', row.part, '{% trans "Purchase stock" %}'); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     if (part.assembly) { | ||||||
|  |                         html += makeIconButton('fa-tools', 'button-build', row.part, '{% trans "Build stock" %}'); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     html += makeIconButton('fa-dollar-sign icon-green', 'button-price', pk, '{% trans "Calculate price" %}'); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 html += makeIconButton('fa-edit icon-blue', 'button-edit', pk, '{% trans "Edit line item" %}'); | ||||||
|  |                 html += makeIconButton('fa-trash-alt icon-red', 'button-delete', pk, '{% trans "Delete line item " %}'); | ||||||
|  |  | ||||||
|  |                 html += `</div>`; | ||||||
|  |  | ||||||
|  |                 return html; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } else { | ||||||
|  |         // Remove the "in stock" column | ||||||
|  |         delete columns['stock']; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     function reloadTable() { | ||||||
|  |         $(table).bootstrapTable('refresh'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Configure callback functions once the table is loaded | ||||||
|  |     function setupCallbacks() { | ||||||
|  |  | ||||||
|  |         // Callback for editing line items | ||||||
|  |         $(table).find('.button-edit').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |  | ||||||
|  |             constructForm(`/api/order/so-line/${pk}/`, { | ||||||
|  |                 fields: { | ||||||
|  |                     quantity: {}, | ||||||
|  |                     reference: {}, | ||||||
|  |                     sale_price: {}, | ||||||
|  |                     sale_price_currency: {}, | ||||||
|  |                     notes: {}, | ||||||
|  |                 }, | ||||||
|  |                 title: '{% trans "Edit Line Item" %}', | ||||||
|  |                 onSuccess: reloadTable, | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Callback for deleting line items | ||||||
|  |         $(table).find('.button-delete').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |  | ||||||
|  |             constructForm(`/api/order/so-line/${pk}/`, { | ||||||
|  |                 method: 'DELETE', | ||||||
|  |                 title: '{% trans "Delete Line Item" %}', | ||||||
|  |                 onSuccess: reloadTable, | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Callback for allocating stock items by serial number | ||||||
|  |         $(table).find('.button-add-by-sn').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |  | ||||||
|  |             // TODO: Migrate this form to the API forms | ||||||
|  |             inventreeGet(`/api/order/so-line/${pk}/`, {}, | ||||||
|  |                 { | ||||||
|  |                     success: function(response) { | ||||||
|  |                         launchModalForm('{% url "so-assign-serials" %}', { | ||||||
|  |                             success: reloadTable, | ||||||
|  |                             data: { | ||||||
|  |                                 line: pk, | ||||||
|  |                                 part: response.part,  | ||||||
|  |                             } | ||||||
|  |                         }); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Callback for allocation stock items to the order | ||||||
|  |         $(table).find('.button-add').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |  | ||||||
|  |             // TODO: Migrate this form to the API forms | ||||||
|  |             launchModalForm(`/order/sales-order/allocation/new/`, { | ||||||
|  |                 success: reloadTable, | ||||||
|  |                 data: { | ||||||
|  |                     line: pk, | ||||||
|  |                 }, | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Callback for creating a new build | ||||||
|  |         $(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]; | ||||||
|  |      | ||||||
|  |             var quantity = 1; | ||||||
|  |      | ||||||
|  |             if (row.allocated < row.quantity) { | ||||||
|  |                 quantity = row.quantity - row.allocated; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // Create a new build order | ||||||
|  |             newBuildOrder({ | ||||||
|  |                 part: pk, | ||||||
|  |                 sales_order: options.order, | ||||||
|  |                 quantity: quantity, | ||||||
|  |                 success: reloadTable | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Callback for purchasing parts | ||||||
|  |         $(table).find('.button-buy').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |  | ||||||
|  |             launchModalForm('{% url "order-parts" %}', { | ||||||
|  |                 data: { | ||||||
|  |                     parts: [ | ||||||
|  |                         pk | ||||||
|  |                     ], | ||||||
|  |                 }, | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Callback for displaying price | ||||||
|  |         $(table).find('.button-price').click(function() { | ||||||
|  |             var pk = $(this).attr('pk'); | ||||||
|  |             var idx = $(this).closest('tr').attr('data-index'); | ||||||
|  |             var row = $(table).bootstrapTable('getData')[idx]; | ||||||
|  |  | ||||||
|  |             launchModalForm( | ||||||
|  |                 '{% url "line-pricing" %}', | ||||||
|  |                 { | ||||||
|  |                     submit_text: '{% trans "Calculate price" %}', | ||||||
|  |                     data: { | ||||||
|  |                         line_item: pk, | ||||||
|  |                         quantity: row.quantity, | ||||||
|  |                     }, | ||||||
|  |                     buttons: [ | ||||||
|  |                         { | ||||||
|  |                             name: 'update_price', | ||||||
|  |                             title: '{% trans "Update Unit Price" %}' | ||||||
|  |                         }, | ||||||
|  |                     ], | ||||||
|  |                     success: reloadTable, | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $(table).inventreeTable({ | ||||||
|  |         onPostBody: setupCallbacks, | ||||||
|  |         name: 'salesorderlineitems', | ||||||
|  |         sidePagination: 'server', | ||||||
|  |         formatNoMatches: function() { | ||||||
|  |             return '{% trans "No matching line items" %}'; | ||||||
|  |         }, | ||||||
|  |         queryParams: filters, | ||||||
|  |         original: options.params, | ||||||
|  |         url: options.url, | ||||||
|  |         showFooter: true, | ||||||
|  |         uniqueId: 'pk', | ||||||
|  |         detailView: show_detail, | ||||||
|  |         detailViewByClick: show_detail, | ||||||
|  |         detailFilter: function(index, row) { | ||||||
|  |             if (pending) { | ||||||
|  |                 // Order is pending | ||||||
|  |                 return row.allocated > 0; | ||||||
|  |             } else { | ||||||
|  |                 return row.fulfilled > 0; | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         detailFormatter: function(index, row, element) { | ||||||
|  |             if (pending) { | ||||||
|  |                 return showAllocationSubTable(index, row, element, options); | ||||||
|  |             } else { | ||||||
|  |                 return showFulfilledSubTable(index, row, element, options); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         columns: columns, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user