diff --git a/InvenTree/order/admin.py b/InvenTree/order/admin.py index 5d843fc624..54e91ed844 100644 --- a/InvenTree/order/admin.py +++ b/InvenTree/order/admin.py @@ -77,6 +77,14 @@ class POLineItemResource(ModelResource): class SOLineItemResource(ModelResource): """ Class for managing import / export of SOLineItem data """ + part_name = Field(attribute='part__name', readonly=True) + + IPN = Field(attribute='part__IPN', readonly=True) + + description = Field(attribute='part__description', readonly=True) + + fulfilled = Field(attribute='fulfilled_quantity', readonly=True) + class Meta: model = SalesOrderLineItem skip_unchanged = True diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html index 69e972da6c..8b98755900 100644 --- a/InvenTree/order/templates/order/order_base.html +++ b/InvenTree/order/templates/order/order_base.html @@ -39,6 +39,9 @@ src="{% static 'img/blank_image.png' %}" + {% if roles.purchase_order.change %} {% endif %} {% endif %} - {% endblock %} @@ -224,7 +224,7 @@ $("#cancel-order").click(function() { }); $("#export-order").click(function() { - location.href = "{% url 'po-export' order.id %}"; + exportOrder('{% url "po-export" order.id %}'); }); diff --git a/InvenTree/order/templates/order/sales_order_base.html b/InvenTree/order/templates/order/sales_order_base.html index 6f8c422f7a..3fd34e42b9 100644 --- a/InvenTree/order/templates/order/sales_order_base.html +++ b/InvenTree/order/templates/order/sales_order_base.html @@ -50,6 +50,9 @@ src="{% static 'img/blank_image.png' %}" + {% if roles.sales_order.change %} {% endif %} {% endif %} + {% endblock %} @@ -196,4 +201,8 @@ $('#print-order-report').click(function() { printSalesOrderReports([{{ order.pk }}]); }); +$('#export-order').click(function() { + exportOrder('{% url "so-export" order.id %}'); +}); + {% endblock %} \ No newline at end of file diff --git a/InvenTree/order/urls.py b/InvenTree/order/urls.py index 37433e02f0..afc689cc23 100644 --- a/InvenTree/order/urls.py +++ b/InvenTree/order/urls.py @@ -36,6 +36,7 @@ purchase_order_urls = [ sales_order_detail_urls = [ url(r'^cancel/', views.SalesOrderCancel.as_view(), name='so-cancel'), url(r'^ship/', views.SalesOrderShip.as_view(), name='so-ship'), + url(r'^export/', views.SalesOrderExport.as_view(), name='so-export'), url(r'^.*$', views.SalesOrderDetail.as_view(), name='so-detail'), ] diff --git a/InvenTree/order/views.py b/InvenTree/order/views.py index 5bb6d161b6..08741faa2e 100644 --- a/InvenTree/order/views.py +++ b/InvenTree/order/views.py @@ -23,7 +23,7 @@ from decimal import Decimal, InvalidOperation from .models import PurchaseOrder, PurchaseOrderLineItem from .models import SalesOrder, SalesOrderLineItem from .models import SalesOrderAllocation -from .admin import POLineItemResource +from .admin import POLineItemResource, SOLineItemResource from build.models import Build from company.models import Company, SupplierPart # ManufacturerPart from stock.models import StockItem @@ -436,6 +436,33 @@ class PurchaseOrderUpload(FileManagementFormView): return HttpResponseRedirect(reverse('po-detail', kwargs={'pk': self.kwargs['pk']})) +class SalesOrderExport(AjaxView): + """ + Export a sales order + + - File format can optionally be passed as a query parameter e.g. ?format=CSV + - Default file format is CSV + """ + + model = SalesOrder + + role_required = 'sales_order.view' + + def get(self, request, *args, **kwargs): + + order = get_object_or_404(SalesOrder, pk=self.kwargs.get('pk', None)) + + export_format = request.GET.get('format', 'csv') + + filename = f"{str(order)} - {order.customer.name}.{export_format}" + + dataset = SOLineItemResource().export(queryset=order.lines.all()) + + filedata = dataset.export(format=export_format) + + return DownloadFile(filedata, filename) + + class PurchaseOrderExport(AjaxView): """ File download for a purchase order @@ -450,7 +477,7 @@ class PurchaseOrderExport(AjaxView): def get(self, request, *args, **kwargs): - order = get_object_or_404(PurchaseOrder, pk=self.kwargs['pk']) + order = get_object_or_404(PurchaseOrder, pk=self.kwargs.get('pk', None)) export_format = request.GET.get('format', 'csv') diff --git a/InvenTree/templates/js/dynamic/inventree.js b/InvenTree/templates/js/dynamic/inventree.js index 5cd7bfcb80..1821c5ef70 100644 --- a/InvenTree/templates/js/dynamic/inventree.js +++ b/InvenTree/templates/js/dynamic/inventree.js @@ -10,6 +10,7 @@ /* exported attachClipboard, enableDragAndDrop, + exportFormatOptions, inventreeDocReady, inventreeLoad, inventreeSave, @@ -46,6 +47,31 @@ function attachClipboard(selector, containerselector, textElement) { } +/** + * Return a standard list of export format options * + */ +function exportFormatOptions() { + return [ + { + value: 'csv', + display_name: 'CSV', + }, + { + value: 'tsv', + display_name: 'TSV', + }, + { + value: 'xls', + display_name: 'XLS', + }, + { + value: 'xlsx', + display_name: 'XLSX', + }, + ]; +} + + function inventreeDocReady() { /* Run this function when the HTML document is loaded. * This will be called for every page that extends "base.html" diff --git a/InvenTree/templates/js/translated/order.js b/InvenTree/templates/js/translated/order.js index f4b1d0b997..7cadfe453d 100644 --- a/InvenTree/templates/js/translated/order.js +++ b/InvenTree/templates/js/translated/order.js @@ -21,6 +21,7 @@ /* exported createSalesOrder, editPurchaseOrderLineItem, + exportOrder, loadPurchaseOrderLineItemTable, loadPurchaseOrderTable, loadSalesOrderAllocationTable, @@ -187,6 +188,49 @@ function newSupplierPartFromOrderWizard(e) { }); } +/** + * Export an order (PurchaseOrder or SalesOrder) + * + * - Display a simple form which presents the user with export options + * + */ +function exportOrder(redirect_url, options={}) { + + var format = options.format; + + // If default format is not provided, lookup + if (!format) { + format = inventreeLoad('order-export-format', 'csv'); + } + + constructFormBody({}, { + title: '{% trans "Export Order" %}', + fields: { + format: { + label: '{% trans "Format" %}', + help_text: '{% trans "Select file format" %}', + required: true, + type: 'choice', + value: format, + choices: exportFormatOptions(), + } + }, + onSubmit: function(fields, opts) { + + var format = getFormFieldValue('format', fields['format'], opts); + + // Save the format for next time + inventreeSave('order-export-format', format); + + // Hide the modal + $(opts.modal).modal('hide'); + + // Download the file! + location.href = `${redirect_url}?format=${format}`; + } + }); +} + function newPurchaseOrderFromOrderWizard(e) { /* Create a new purchase order directly from an order form. * Launches a secondary modal and (if successful), diff --git a/InvenTree/templates/js/translated/stock.js b/InvenTree/templates/js/translated/stock.js index b88f5f1862..67c50bffef 100644 --- a/InvenTree/templates/js/translated/stock.js +++ b/InvenTree/templates/js/translated/stock.js @@ -98,24 +98,7 @@ function exportStock(params={}) { required: true, type: 'choice', value: 'csv', - choices: [ - { - value: 'csv', - display_name: 'CSV', - }, - { - value: 'tsv', - display_name: 'TSV', - }, - { - value: 'xls', - display_name: 'XLS', - }, - { - value: 'xlsx', - display_name: 'XLSX', - }, - ], + choices: exportFormatOptions(), }, sublocations: { label: '{% trans "Include Sublocations" %}',