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" %}',