From 727fd389783bf3dc033a689b2ba676252c728f6d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 16:48:20 +1100 Subject: [PATCH 01/15] Add new report models --- ...14_purchaseorderreport_salesorderreport.py | 45 +++++++++++ InvenTree/report/models.py | 75 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 InvenTree/report/migrations/0014_purchaseorderreport_salesorderreport.py diff --git a/InvenTree/report/migrations/0014_purchaseorderreport_salesorderreport.py b/InvenTree/report/migrations/0014_purchaseorderreport_salesorderreport.py new file mode 100644 index 0000000000..ab734b7b48 --- /dev/null +++ b/InvenTree/report/migrations/0014_purchaseorderreport_salesorderreport.py @@ -0,0 +1,45 @@ +# Generated by Django 3.0.7 on 2021-03-10 05:46 + +import django.core.validators +from django.db import migrations, models +import report.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('report', '0013_testreport_include_installed'), + ] + + operations = [ + migrations.CreateModel( + name='PurchaseOrderReport', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Template name', max_length=100, verbose_name='Name')), + ('template', models.FileField(help_text='Report template file', upload_to=report.models.rename_template, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Template')), + ('description', models.CharField(help_text='Report template description', max_length=250, verbose_name='Description')), + ('revision', models.PositiveIntegerField(default=1, editable=False, help_text='Report revision number (auto-increments)', verbose_name='Revision')), + ('enabled', models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled')), + ('filters', models.CharField(blank=True, help_text='Purchase order query filters', max_length=250, validators=[report.models.validate_purchase_order_filters], verbose_name='Filters')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SalesOrderReport', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Template name', max_length=100, verbose_name='Name')), + ('template', models.FileField(help_text='Report template file', upload_to=report.models.rename_template, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Template')), + ('description', models.CharField(help_text='Report template description', max_length=250, verbose_name='Description')), + ('revision', models.PositiveIntegerField(default=1, editable=False, help_text='Report revision number (auto-increments)', verbose_name='Revision')), + ('enabled', models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled')), + ('filters', models.CharField(blank=True, help_text='Sales order query filters', max_length=250, validators=[report.models.validate_sales_order_filters], verbose_name='Filters')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index abbdea9025..53ed655d72 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -24,6 +24,7 @@ import build.models import common.models import part.models import stock.models +import order.models from InvenTree.helpers import validateFilterString @@ -94,6 +95,22 @@ def validate_build_report_filters(filters): return validateFilterString(filters, model=build.models.Build) +def validate_purchase_order_filters(filters): + """ + Validate filter string against PurchaseOrder model + """ + + return validateFilterString(filters, model=order.models.PurchaseOrder) + + +def validate_sales_order_filters(filters): + """ + Validate filter string against SalesOrder model + """ + + return validateFilterString(filters, model=order.models.SalesOrder) + + class WeasyprintReportMixin(WeasyTemplateResponseMixin): """ Class for rendering a HTML template to a PDF. @@ -383,6 +400,64 @@ class BillOfMaterialsReport(ReportTemplateBase): } +class PurchaseOrderReport(ReportTemplateBase): + """ + Render a report against a PurchaseOrder object + """ + + @classmethod + def getSubdir(cls): + return 'purchaseorder' + + filters = models.CharField( + blank=True, + max_length=250, + verbose_name=_('Filters'), + help_text=_('Purchase order query filters'), + validators=[ + validate_purchase_order_filters, + ] + ) + + def get_context_data(self, request): + + order = self.object_to_print + + return { + 'order': order, + 'supplier': order.supplier, + } + + +class SalesOrderReport(ReportTemplateBase): + """ + Render a report against a SalesOrder object + """ + + @classmethod + def getSubdir(cls): + return 'salesorder' + + filters = models.CharField( + blank=True, + max_length=250, + verbose_name=_('Filters'), + help_text=_('Sales order query filters'), + validators=[ + validate_sales_order_filters + ] + ) + + def get_context_data(self, request): + + order = self.object_to_print + + return { + 'order': order, + 'customer': order.customer, + } + + def rename_snippet(instance, filename): filename = os.path.basename(filename) From 7f054859544792ec976ebd01187cc850e5252b1f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 16:50:55 +1100 Subject: [PATCH 02/15] Add new reports to the admin interface --- InvenTree/report/admin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/InvenTree/report/admin.py b/InvenTree/report/admin.py index 2c008877cc..b76b8fab1b 100644 --- a/InvenTree/report/admin.py +++ b/InvenTree/report/admin.py @@ -7,6 +7,8 @@ from .models import ReportSnippet, ReportAsset from .models import TestReport from .models import BuildReport from .models import BillOfMaterialsReport +from .models import PurchaseOrderReport +from .models import SalesOrderReport class ReportTemplateAdmin(admin.ModelAdmin): @@ -30,3 +32,5 @@ admin.site.register(ReportAsset, ReportAssetAdmin) admin.site.register(TestReport, ReportTemplateAdmin) admin.site.register(BuildReport, ReportTemplateAdmin) admin.site.register(BillOfMaterialsReport, ReportTemplateAdmin) +admin.site.register(PurchaseOrderReport, ReportTemplateAdmin) +admin.site.register(SalesOrderReport, ReportTemplateAdmin) From 9b0595d232219ef5442869565a1da036496f0f44 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 16:53:02 +1100 Subject: [PATCH 03/15] Add serializers --- InvenTree/report/serializers.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/InvenTree/report/serializers.py b/InvenTree/report/serializers.py index f0a449ae49..fa7de1a3ea 100644 --- a/InvenTree/report/serializers.py +++ b/InvenTree/report/serializers.py @@ -7,6 +7,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField from .models import TestReport from .models import BuildReport from .models import BillOfMaterialsReport +from .models import PurchaseOrderReport, SalesOrderReport class TestReportSerializer(InvenTreeModelSerializer): @@ -55,3 +56,35 @@ class BOMReportSerializer(InvenTreeModelSerializer): 'filters', 'enabled', ] + + +class POReportSerializer(InvenTreeModelSerializer): + + template = InvenTreeAttachmentSerializerField(required=True) + + class Meta: + model = PurchaseOrderReport + fields = [ + 'pk', + 'name', + 'description', + 'template', + 'filters', + 'enabled', + ] + + +class SOReportSerializer(InvenTreeModelSerializer): + + template = InvenTreeAttachmentSerializerField(required=True) + + class Meta: + model = SalesOrderReport + fields = [ + 'pk', + 'name', + 'description', + 'template', + 'filters', + 'enabled', + ] From 33e176e4e7f71fb83684d46a32bbbcdef388112c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 17:09:37 +1100 Subject: [PATCH 04/15] Add list view API endpoints --- InvenTree/report/api.py | 168 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index 313d4de88d..58376c4b33 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -18,14 +18,19 @@ from stock.models import StockItem import build.models import part.models +import order.models from .models import TestReport from .models import BuildReport from .models import BillOfMaterialsReport +from .models import PurchaseOrderReport +from .models import SalesOrderReport from .serializers import TestReportSerializer from .serializers import BuildReportSerializer from .serializers import BOMReportSerializer +from .serializers import POReportSerializer +from .serializers import SOReportSerializer class ReportListView(generics.ListAPIView): @@ -113,6 +118,40 @@ class BuildReportMixin: return build.models.Build.objects.filter(pk__in=valid_ids) +class OrderReportMixin: + """ + Mixin for extracting order items from query params + + requires the OrderModel class attribute to be set! + """ + + def get_orders(self): + """ + Return a list of order objects + """ + + orders = [] + + params = self.request.query_params + + for key in ['order', 'order[]', 'orders', 'orders[]']: + if key in params: + orders = params.getlist(key, []) + break + + valid_ids = [] + + for order in orders: + try: + valid_ids.append(int(order)) + except (ValueError): + pass + + valid_orders = self.OrderModel.objects.filter(pk__in=valid_ids) + + return valid_orders + + class PartReportMixin: """ Mixin for extracting part items from query params @@ -481,8 +520,137 @@ class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMi return self.print(request, builds) +class POReportList(ReportListView, OrderReportMixin): + + OrderModel = order.models.PurchaseOrder + + queryset = PurchaseOrderReport.objects.all() + serializer_class = POReportSerializer + + def filter_queryset(self, queryset): + + queryset = super().filter_queryset(queryset) + + orders = self.get_orders() + + if len(orders) > 0: + """ + We wish to filter by purchase orders + + We need to compare the 'filters' string of each report, + and see if it matches against each of the specified orders. + + TODO: In the future, perhaps there is a way to make this more efficient. + """ + + valid_report_ids = set() + + for report in queryset.all(): + + matches = True + + # Filter string defined for the report object + try: + filters = InvenTree.helpers.validateFilterString(report.filters) + except: + continue + + for order in orders: + order_query = order.models.PurchaseOrder.objects.filter(pk=order.pk) + + try: + if not order_query.filter(**filters).exists(): + matches = False + break + except FieldError: + matches = False + break + + if matches: + valid_report_ids.add(report.pk) + else: + continue + + # Reduce queryset to only valid matches + queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids]) + + return queryset + + +class SOReportList(ReportListView, OrderReportMixin): + + OrderModel = order.models.SalesOrder + + queryset = SalesOrderReport.objects.all() + serializer_class = SOReportSerializer + + def filter_queryset(self, queryset): + + queryset = super().filter_queryset(queryset) + + orders = self.get_orders() + + if len(orders) > 0: + """ + We wish to filter by purchase orders + + We need to compare the 'filters' string of each report, + and see if it matches against each of the specified orders. + + TODO: In the future, perhaps there is a way to make this more efficient. + """ + + valid_report_ids = set() + + for report in queryset.all(): + + matches = True + + # Filter string defined for the report object + try: + filters = InvenTree.helpers.validateFilterString(report.filters) + except: + continue + + for order in orders: + order_query = order.models.SalesOrder.objects.filter(pk=order.pk) + + try: + if not order_query.filter(**filters).exists(): + matches = False + break + except FieldError: + matches = False + break + + if matches: + valid_report_ids.add(report.pk) + else: + continue + + # Reduce queryset to only valid matches + queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids]) + + return queryset + + report_api_urls = [ + # Purchase order reports + url(r'po/', include([ + # Detail views. + + # List view + url(r'^$', POReportList.as_view(), name='api-po-report-list'), + ])), + + # Sales order reports + url(r'so/', include([ + # Detail views + + url(r'^$', SOReportList.as_view(), name='api-so-report-list'), + ])), + # Build reports url(r'build/', include([ # Detail views From 5a6a12604ebd6350296b3a86d2ed5ac97f98f1e7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 17:13:19 +1100 Subject: [PATCH 05/15] Add detail endpoints --- InvenTree/report/api.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index 58376c4b33..70f9625dab 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -577,6 +577,15 @@ class POReportList(ReportListView, OrderReportMixin): return queryset +class POReportDetail(generics.RetrieveUpdateDestroyAPIView): + """ + API endpoint for a single PurchaseOrderReport object + """ + + queryset = PurchaseOrderReport.objects.all() + serializer_class = POReportSerializer + + class SOReportList(ReportListView, OrderReportMixin): OrderModel = order.models.SalesOrder @@ -634,11 +643,23 @@ class SOReportList(ReportListView, OrderReportMixin): return queryset +class SOReportDetail(generics.RetrieveUpdateDestroyAPIView): + """ + API endpoint for a single SalesOrderReport object + """ + + queryset = SalesOrderReport.objects.all() + serializer_class = SOReportSerializer + + report_api_urls = [ # Purchase order reports url(r'po/', include([ - # Detail views. + # Detail views + url(r'^(?P\d+)/', include([ + url(r'^$', POReportDetail.as_view(), name='api-po-report-detail'), + ])), # List view url(r'^$', POReportList.as_view(), name='api-po-report-list'), @@ -647,6 +668,9 @@ report_api_urls = [ # Sales order reports url(r'so/', include([ # Detail views + url(r'^(?P\d+)/', include([ + url(r'^$', SOReportDetail.as_view(), name='api-so-report-detail'), + ])), url(r'^$', SOReportList.as_view(), name='api-so-report-list'), ])), @@ -656,7 +680,7 @@ report_api_urls = [ # Detail views url(r'^(?P\d+)/', include([ url(r'print/?', BuildReportPrint.as_view(), name='api-build-report-print'), - url(r'^.*$', BuildReportDetail.as_view(), name='api-build-report-detail'), + url(r'^.$', BuildReportDetail.as_view(), name='api-build-report-detail'), ])), # List view From 7800664f4b07c6a32b60e806dc945bbabc3c3ee7 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Wed, 10 Mar 2021 18:29:22 +1100 Subject: [PATCH 06/15] Add printing endpoints --- InvenTree/report/api.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index 70f9625dab..7f39f16f7f 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -586,6 +586,23 @@ class POReportDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = POReportSerializer +class POReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin): + """ + API endpoint for printing a PurchaseOrderReport object + """ + + OrderModel = order.models.PurchaseOrder + + queryset = PurchaseOrderReport.objects.all() + serializer_class = POReportSerializer + + def get(self, request, *args, **kwargs): + + orders = self.get_orders() + + return self.print(request, orders) + + class SOReportList(ReportListView, OrderReportMixin): OrderModel = order.models.SalesOrder @@ -652,12 +669,30 @@ class SOReportDetail(generics.RetrieveUpdateDestroyAPIView): serializer_class = SOReportSerializer +class SOReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin): + """ + API endpoint for printing a PurchaseOrderReport object + """ + + OrderModel = order.models.SalesOrder + + queryset = SalesOrderReport.objects.all() + serializer_class = SOReportSerializer + + def get(self, request, *args, **kwargs): + + orders = self.get_orders() + + return self.print(request, orders) + + report_api_urls = [ # Purchase order reports url(r'po/', include([ # Detail views url(r'^(?P\d+)/', include([ + url(r'print/', POReportPrint.as_view(), name='api-po-report-print'), url(r'^$', POReportDetail.as_view(), name='api-po-report-detail'), ])), @@ -669,6 +704,7 @@ report_api_urls = [ url(r'so/', include([ # Detail views url(r'^(?P\d+)/', include([ + url(r'print/', SOReportPrint.as_view(), name='api-so-report-print'), url(r'^$', SOReportDetail.as_view(), name='api-so-report-detail'), ])), From fa95759a009539a2d3ebe5a18321e2290994c64b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 14:09:57 +1100 Subject: [PATCH 07/15] Enable printing for PO and SO --- .../order/templates/order/order_base.html | 9 +- .../templates/order/sales_order_base.html | 7 ++ InvenTree/report/api.py | 8 +- InvenTree/templates/js/report.js | 108 ++++++++++++++++++ 4 files changed, 127 insertions(+), 5 deletions(-) diff --git a/InvenTree/order/templates/order/order_base.html b/InvenTree/order/templates/order/order_base.html index ed90e1f049..649dbda811 100644 --- a/InvenTree/order/templates/order/order_base.html +++ b/InvenTree/order/templates/order/order_base.html @@ -35,7 +35,10 @@ src="{% static 'img/blank_image.png' %}"

{{ order.description }}

-
+
+ {% if roles.purchase_order.change %} {% if roles.sales_order.change %} - {% endif %} - - -
- +
+ {% if roles.purchase_order.add %} + + {% endif %} + + + +
+ +
@@ -154,6 +160,18 @@ $("#view-list").click(function() { $("#view-calendar").show(); }); +$("#order-print").click(function() { + var rows = $("#purchase-order-table").bootstrapTable('getSelections'); + + var orders = []; + + rows.forEach(function(row) { + orders.push(row.pk); + }); + + printPurchaseOrderReports(orders); +}) + $("#po-create").click(function() { launchModalForm("{% url 'po-create' %}", { diff --git a/InvenTree/order/templates/order/sales_orders.html b/InvenTree/order/templates/order/sales_orders.html index 257ee13887..6d4be9fff6 100644 --- a/InvenTree/order/templates/order/sales_orders.html +++ b/InvenTree/order/templates/order/sales_orders.html @@ -15,18 +15,24 @@ InvenTree | {% trans "Sales Orders" %}
- {% if roles.sales_order.add %} - - {% endif %} - - -
- +
+ {% if roles.sales_order.add %} + + {% endif %} + + + +
+ +
@@ -156,10 +162,30 @@ loadSalesOrderTable("#sales-order-table", { url: "{% url 'api-so-list' %}", }); +$("#order-print").click(function() { + var rows = $("#sales-order-table").bootstrapTable('getSelections'); + + var orders = []; + + rows.forEach(function(row) { + orders.push(row.pk); + }); + + printSalesOrderReports(orders); +}) + $("#so-create").click(function() { launchModalForm("{% url 'so-create' %}", { follow: true, + secondary: [ + { + field: 'customer', + label: '{% trans "New Customer" %}', + title: '{% trans "Create new Customer" %}', + url: '{% url "customer-create" %}', + } + ] } ); }); diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 92b9db787c..39981226ca 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -139,8 +139,9 @@ function loadPurchaseOrderTable(table, options) { columns: [ { field: 'pk', - title: 'ID', - visible: false, + title: '', + visible: true, + checkbox: true, switchable: false, }, { @@ -235,8 +236,9 @@ function loadSalesOrderTable(table, options) { columns: [ { field: 'pk', - title: 'ID', - visible: false, + title: '', + checkbox: true, + visible: true, switchable: false, }, { From 8e2a2c59bf38512c15510f17f0ab37330a9b4ab9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 14:19:25 +1100 Subject: [PATCH 09/15] Add more context data to reports --- InvenTree/report/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 53ed655d72..24bb6da219 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -424,8 +424,12 @@ class PurchaseOrderReport(ReportTemplateBase): order = self.object_to_print return { + 'description': order.description, 'order': order, + 'reference': order.reference, 'supplier': order.supplier, + 'prefix': common.models.InvenTreeSetting.get_setting('PURCHASEORDER_REFERENCE_PREFIX'), + 'title': str(order), } @@ -453,8 +457,12 @@ class SalesOrderReport(ReportTemplateBase): order = self.object_to_print return { - 'order': order, 'customer': order.customer, + 'description': order.description, + 'order': order, + 'prefix': common.models.InvenTreeSetting.get_setting('SALESORDER_REFERENCE_PREFIX'), + 'reference': order.reference, + 'title': str(order), } From e1ba0a9a99289bfdc2c77717761a12faf2496724 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 14:24:28 +1100 Subject: [PATCH 10/15] Bug fix for tables --- InvenTree/templates/js/order.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/order.js index 39981226ca..fa60ebdf6d 100644 --- a/InvenTree/templates/js/order.js +++ b/InvenTree/templates/js/order.js @@ -138,7 +138,6 @@ function loadPurchaseOrderTable(table, options) { formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; }, columns: [ { - field: 'pk', title: '', visible: true, checkbox: true, @@ -235,7 +234,6 @@ function loadSalesOrderTable(table, options) { formatNoMatches: function() { return '{% trans "No sales orders found" %}'; }, columns: [ { - field: 'pk', title: '', checkbox: true, visible: true, From 9d321f483344db54d709dad2d26367394b0ea6ad Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 14:47:45 +1100 Subject: [PATCH 11/15] Removed --- .../templates/report/inventree_build_order_base.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/InvenTree/report/templates/report/inventree_build_order_base.html b/InvenTree/report/templates/report/inventree_build_order_base.html index 46e7544df5..6c8af84f1d 100644 --- a/InvenTree/report/templates/report/inventree_build_order_base.html +++ b/InvenTree/report/templates/report/inventree_build_order_base.html @@ -77,12 +77,9 @@ margin-top: 4cm; content: "v{{report_revision}} - {{ date.isoformat }}"; {% endblock %} -{% block bottom_center %} -content: "www.currawong.aero"; -{% endblock %} - {% block header_content %} - + +

From eb6310c774166ec8652801d2ed341a5e336c5ecc Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 15:01:15 +1100 Subject: [PATCH 12/15] Render company image to report --- InvenTree/report/models.py | 2 ++ InvenTree/report/templatetags/report.py | 34 +++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index 24bb6da219..a838396bf0 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -425,6 +425,7 @@ class PurchaseOrderReport(ReportTemplateBase): return { 'description': order.description, + 'lines': order.lines, 'order': order, 'reference': order.reference, 'supplier': order.supplier, @@ -459,6 +460,7 @@ class SalesOrderReport(ReportTemplateBase): return { 'customer': order.customer, 'description': order.description, + 'lines': order.lines, 'order': order, 'prefix': common.models.InvenTreeSetting.get_setting('SALESORDER_REFERENCE_PREFIX'), 'reference': order.reference, diff --git a/InvenTree/report/templatetags/report.py b/InvenTree/report/templatetags/report.py index 9cd28139f2..8ff15ccdee 100644 --- a/InvenTree/report/templatetags/report.py +++ b/InvenTree/report/templatetags/report.py @@ -8,6 +8,7 @@ from django import template from django.conf import settings from django.utils.safestring import mark_safe +from company.models import Company from part.models import Part from stock.models import StockItem @@ -72,6 +73,39 @@ def part_image(part): return f"file://{path}" +@register.simple_tag() +def company_image(company): + """ + Return a fully-qualified path for a company image + """ + + # If in debug mode, return the URL to the image, not a local file + debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE') + + if type(company) is Company: + img = company.image.name + else: + img = '' + + if debug_mode: + if img: + return os.path.join(settings.MEDIA_URL, img) + else: + return os.path.join(settings.STATIC_URL, 'img', 'blank_image.png') + + else: + path = os.path.join(settings.MEDIA_ROOT, img) + path = os.path.abspath(path) + + if not os.path.exists(path) or not os.path.isfile(path): + # Image does not exist + # Return the 'blank' image + path = os.path.join(settings.STATIC_ROOT, 'img', 'blank_image.png') + path = os.path.abspath(path) + + return f"file://{path}" + + @register.simple_tag() def internal_link(link, text): """ From f1ba20c3da3690f2249399a893af4529f7af7187 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 15:01:25 +1100 Subject: [PATCH 13/15] Basic PO and SO reports --- .../templates/report/inventree_po_report.html | 116 ++++++++++++++++++ .../templates/report/inventree_so_report.html | 116 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 InvenTree/report/templates/report/inventree_po_report.html create mode 100644 InvenTree/report/templates/report/inventree_so_report.html diff --git a/InvenTree/report/templates/report/inventree_po_report.html b/InvenTree/report/templates/report/inventree_po_report.html new file mode 100644 index 0000000000..2be71f6c38 --- /dev/null +++ b/InvenTree/report/templates/report/inventree_po_report.html @@ -0,0 +1,116 @@ +{% extends "report/inventree_report_base.html" %} + +{% load i18n %} +{% load report %} +{% load barcode %} +{% load inventree_extras %} +{% load markdownify %} + +{% block page_margin %} +margin: 2cm; +margin-top: 4cm; +{% endblock %} + +{% block bottom_left %} +content: "v{{report_revision}} - {{ date.isoformat }}"; +{% endblock %} + +{% block bottom_center %} +content: "InvenTree v{% inventree_version %}"; +{% endblock %} + +{% block style %} + +.header-right { + text-align: right; + float: right; +} + +.logo { + height: 20mm; + vertical-align: middle; +} + +.thumb-container { + width: 32px; + display: inline; +} + + +.part-thumb { + max-width: 32px; + max-height: 32px; + display: inline; +} + +.part-text { + display: inline; +} + +table { + border: 1px solid #eee; + border-radius: 3px; + border-collapse: collapse; + width: 100%; + font-size: 80%; +} + +table td { + border: 1px solid #eee; +} + +table td.shrink { + white-space: nowrap +} + +table td.expand { + width: 99% +} + +{% endblock %} + +{% block header_content %} + + + +
+

{% trans "Purchase Order" %} {{ prefix }}{{ reference }}

+ {{ supplier.name }} +
+ +{% endblock %} + +{% block page_content %} + +

{% trans "Line Items" %}

+ + + + + + + + + + + + {% for line in lines.all %} + + + + + + + {% endfor %} + +
{% trans "Part" %}{% trans "Quantity" %}{% trans "Reference" %}{% trans "Note" %}
+
+ +
+
+ {{ line.part.part.full_name }} +
+
{% decimal line.quantity %}{{ line.reference }}{{ line.notes }}
+ + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/report/templates/report/inventree_so_report.html b/InvenTree/report/templates/report/inventree_so_report.html new file mode 100644 index 0000000000..255f0c6a50 --- /dev/null +++ b/InvenTree/report/templates/report/inventree_so_report.html @@ -0,0 +1,116 @@ +{% extends "report/inventree_report_base.html" %} + +{% load i18n %} +{% load report %} +{% load barcode %} +{% load inventree_extras %} +{% load markdownify %} + +{% block page_margin %} +margin: 2cm; +margin-top: 4cm; +{% endblock %} + +{% block bottom_left %} +content: "v{{report_revision}} - {{ date.isoformat }}"; +{% endblock %} + +{% block bottom_center %} +content: "InvenTree v{% inventree_version %}"; +{% endblock %} + +{% block style %} + +.header-right { + text-align: right; + float: right; +} + +.logo { + height: 20mm; + vertical-align: middle; +} + +.thumb-container { + width: 32px; + display: inline; +} + + +.part-thumb { + max-width: 32px; + max-height: 32px; + display: inline; +} + +.part-text { + display: inline; +} + +table { + border: 1px solid #eee; + border-radius: 3px; + border-collapse: collapse; + width: 100%; + font-size: 80%; +} + +table td { + border: 1px solid #eee; +} + +table td.shrink { + white-space: nowrap +} + +table td.expand { + width: 99% +} + +{% endblock %} + +{% block header_content %} + + + +
+

{% trans "Sales Order" %} {{ prefix }}{{ reference }}

+ {{ customer.name }} +
+ +{% endblock %} + +{% block page_content %} + +

{% trans "Line Items" %}

+ + + + + + + + + + + + {% for line in lines.all %} + + + + + + + {% endfor %} + +
{% trans "Part" %}{% trans "Quantity" %}{% trans "Reference" %}{% trans "Note" %}
+
+ +
+
+ {{ line.part.full_name }} +
+
{% decimal line.quantity %}{{ line.reference }}{{ line.notes }}
+ + +{% endblock %} \ No newline at end of file From 5949ccd74f38ea5909c61fa0794cb10b19667fd1 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 11 Mar 2021 17:11:57 +1100 Subject: [PATCH 14/15] Bug fix --- InvenTree/report/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index 16c512ed6c..212c57bd7f 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -141,9 +141,9 @@ class OrderReportMixin: valid_ids = [] - for order in orders: + for o in orders: try: - valid_ids.append(int(order)) + valid_ids.append(int(o)) except (ValueError): pass From c07f217416b09b28aec0515cee5f7e9f3b9acff5 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Fri, 12 Mar 2021 14:01:20 +1100 Subject: [PATCH 15/15] Add "ignore" rules for new report models --- InvenTree/users/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py index 903bc10dfa..5c95abfe46 100644 --- a/InvenTree/users/models.py +++ b/InvenTree/users/models.py @@ -124,6 +124,8 @@ class RuleSet(models.Model): 'report_reportasset', 'report_reportsnippet', 'report_billofmaterialsreport', + 'report_purchaseorderreport', + 'report_salesorderreport', 'users_owner', # Third-party tables