diff --git a/InvenTree/report/apps.py b/InvenTree/report/apps.py
index bb1c5f0cb7..941133e481 100644
--- a/InvenTree/report/apps.py
+++ b/InvenTree/report/apps.py
@@ -18,6 +18,66 @@ class ReportConfig(AppConfig):
"""
self.create_default_test_reports()
+ self.create_default_build_reports()
+
+ def create_default_reports(self, model, reports):
+ """
+ Copy defualt report files across to the media directory.
+ """
+
+ # Source directory for report templates
+ src_dir = os.path.join(
+ os.path.dirname(os.path.realpath(__file__)),
+ 'templates',
+ 'report',
+ )
+
+ # Destination directory
+ dst_dir = os.path.join(
+ settings.MEDIA_ROOT,
+ 'report',
+ 'inventree',
+ model.getSubdir(),
+ )
+
+ if not os.path.exists(dst_dir):
+ logger.info(f"Creating missing directory: '{dst_dir}'")
+ os.makedirs(dst_dir, exist_ok=True)
+
+ # Copy each report template across (if required)
+ for report in reports:
+
+ # Destination filename
+ filename = os.path.join(
+ 'report',
+ 'inventree',
+ model.getSubdir(),
+ report['file'],
+ )
+
+ src_file = os.path.join(src_dir, report['file'])
+ dst_file = os.path.join(settings.MEDIA_ROOT, filename)
+
+ if not os.path.exists(dst_file):
+ logger.info(f"Copying test report template '{dst_file}'")
+ shutil.copyfile(src_file, dst_file)
+
+ try:
+ # Check if a report matching the template already exists
+ if model.objects.filter(template=filename).exists():
+ continue
+
+ logger.info(f"Creating new TestReport for '{report['name']}'")
+
+ model.objects.create(
+ name=report['name'],
+ description=report['description'],
+ template=filename,
+ enabled=True
+ )
+
+ except:
+ pass
def create_default_test_reports(self):
"""
@@ -31,23 +91,6 @@ class ReportConfig(AppConfig):
# Database is not ready yet
return
- src_dir = os.path.join(
- os.path.dirname(os.path.realpath(__file__)),
- 'templates',
- 'report',
- )
-
- dst_dir = os.path.join(
- settings.MEDIA_ROOT,
- 'report',
- 'inventree', # Stored in secret directory!
- 'test',
- )
-
- if not os.path.exists(dst_dir):
- logger.info(f"Creating missing directory: '{dst_dir}'")
- os.makedirs(dst_dir, exist_ok=True)
-
# List of test reports to copy across
reports = [
{
@@ -57,36 +100,27 @@ class ReportConfig(AppConfig):
},
]
- for report in reports:
+ self.create_default_reports(TestReport, reports)
- # Create destination file name
- filename = os.path.join(
- 'report',
- 'inventree',
- 'test',
- report['file']
- )
+ def create_default_build_reports(self):
+ """
+ Create database entries for the default BuildReport templates
+ (if they do not already exist)
+ """
- src_file = os.path.join(src_dir, report['file'])
- dst_file = os.path.join(settings.MEDIA_ROOT, filename)
+ try:
+ from .models import BuildReport
+ except:
+ # Database is not ready yet
+ return
- if not os.path.exists(dst_file):
- logger.info(f"Copying test report template '{dst_file}'")
- shutil.copyfile(src_file, dst_file)
+ # List of Build reports to copy across
+ reports = [
+ {
+ 'file': 'inventree_build_order.html',
+ 'name': 'InvenTree Build Order',
+ 'description': 'Build Order job sheet',
+ }
+ ]
- try:
- # Check if a report matching the template already exists
- if TestReport.objects.filter(template=filename).exists():
- continue
-
- logger.info(f"Creating new TestReport for '{report['name']}'")
-
- TestReport.objects.create(
- name=report['name'],
- description=report['description'],
- template=filename,
- filters='',
- enabled=True
- )
- except:
- pass
+ self.create_default_reports(BuildReport, reports)
diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py
index 906b65be0b..83810f7344 100644
--- a/InvenTree/report/models.py
+++ b/InvenTree/report/models.py
@@ -62,7 +62,6 @@ class ReportFileUpload(FileSystemStorage):
def get_available_name(self, name, max_length=None):
- print("Name:", name)
return super().get_available_name(name, max_length)
@@ -128,7 +127,8 @@ class ReportBase(models.Model):
def __str__(self):
return "{n} - {d}".format(n=self.name, d=self.description)
- def getSubdir(self):
+ @classmethod
+ def getSubdir(cls):
return ''
def rename_file(self, filename):
@@ -267,7 +267,8 @@ class TestReport(ReportTemplateBase):
Render a TestReport against a StockItem object.
"""
- def getSubdir(self):
+ @classmethod
+ def getSubdir(cls):
return 'test'
filters = models.CharField(
@@ -313,7 +314,8 @@ class BuildReport(ReportTemplateBase):
Build order / work order report
"""
- def getSubdir(self):
+ @classmethod
+ def getSubdir(cls):
return 'build'
filters = models.CharField(
@@ -349,7 +351,8 @@ class BillOfMaterialsReport(ReportTemplateBase):
Render a Bill of Materials against a Part object
"""
- def getSubdir(self):
+ @classmethod
+ def getSubdir(cls):
return 'bom'
filters = models.CharField(
diff --git a/InvenTree/report/templates/report/inventree_build_order.html b/InvenTree/report/templates/report/inventree_build_order.html
new file mode 100644
index 0000000000..72e52a889a
--- /dev/null
+++ b/InvenTree/report/templates/report/inventree_build_order.html
@@ -0,0 +1,3 @@
+{% extends "report/inventree_build_order_base.html" %}
+
+
diff --git a/InvenTree/report/templates/report/inventree_build_order_base.html b/InvenTree/report/templates/report/inventree_build_order_base.html
new file mode 100644
index 0000000000..6e8959ffbf
--- /dev/null
+++ b/InvenTree/report/templates/report/inventree_build_order_base.html
@@ -0,0 +1,185 @@
+{% extends "report/inventree_report_base.html" %}
+
+{% load i18n %}
+{% load report %}
+{% load inventree_extras %}
+{% load markdownify %}
+
+{% block page_margin %}
+margin: 2cm;
+margin-top: 4cm;
+{% endblock %}
+
+{% block style %}
+
+.header-right {
+ text-align: right;
+ float: right;
+}
+
+.logo {
+ height: 20mm;
+ vertical-align: middle;
+}
+
+.float-right {
+ float: right;
+}
+
+.part-image {
+ border: 1px solid;
+ border-radius: 2px;
+ vertical-align: middle;
+ height: 40mm;
+ display: inline-block;
+ z-index: 100;
+}
+
+.details-image {
+ max-width: 25%;
+ float: right;
+}
+
+.details {
+ width: 100%;
+ border: 1px solid;
+ border-radius: 3px;
+ padding: 5px;
+ min-height: 42mm;
+}
+
+.details table {
+ overflow-x: scroll
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ width: 70%;
+ table-layout: fixed;
+ font-size: 75%;
+}
+
+.details table td:not(:last-child){
+ white-space: nowrap;
+}
+
+.details table td:last-child{
+ width: 50%;
+ padding-left: 1cm;
+ padding-right: 1cm;
+}
+
+.details-table td {
+ padding-left: 10px;
+ padding-top: 5px;
+ padding-bottom: 5px;
+ border-bottom: 1px solid #555;
+}
+
+{% endblock %}
+
+{% block bottom_left %}
+content: "v{{report_revision}} - {{ date.isoformat }}";
+{% endblock %}
+
+{% block bottom_center %}
+content: "www.currawong.aero";
+{% endblock %}
+
+{% block header_content %}
+
+
+
{% trans "Build Order" %} | +{% internal_link build.get_absolute_url build %} | +
---|---|
{% trans "Part" %} | +{% internal_link part.get_absolute_url part.full_name %} | +
{% trans "Quantity" %} | +{{ build.quantity }} | +
{% trans "Description" %} | +{{ build.title }} | +
{% trans "Issued" %} | +{{ build.creation_date }} | +
{% trans "Target Date" %} | ++ {% if build.target_date %} + {{ build.target_date }} + {% else %} + Not specified + {% endif %} + | +
{% trans "Sales Order" %} | ++ {% if build.sales_order %} + {% internal_link build.sales_order.get_absolute_url build.sales_order %} + {% else %} + Not specified + {% endif %} + | +
{% trans "Required For" %} | +{% internal_link build.parent.get_absolute_url build.parent %} | +
{% trans "Issued By" %} | +{{ build.issued_by }} | +
{% trans "Responsible" %} | +{{ build.responsible }} | +
{% trans "Link" %} | +{{ build.link }} | +