mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Add default build order report
Toot toot refactor tractor
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| {% extends "report/inventree_build_order_base.html" %} | ||||
|  | ||||
| <!-- Refer to the inventree_build_order_base template --> | ||||
| @@ -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 %} | ||||
|     <img class='logo' src="{% asset 'logo_black_with_black_bird.png' %}" alt="hello" width="150"> | ||||
|  | ||||
|     <div class='header-right'> | ||||
|         <h3> | ||||
|             Build Order {{ build }} | ||||
|         </h3> | ||||
|         <small>{{ quantity }} x {{ part.full_name }}</small> | ||||
|         <br> | ||||
|     </div> | ||||
|  | ||||
|     <hr> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block page_content %} | ||||
|  | ||||
| <div class='details'> | ||||
|     <div class='details-image float-right'> | ||||
|         <img class='part-image' src="{% part_image part %}"> | ||||
|     </div> | ||||
|      | ||||
|     <div class='details-container'> | ||||
|  | ||||
|         <table class='details-table'> | ||||
|             <tr> | ||||
|                 <th>{% trans "Build Order" %}</th> | ||||
|                 <td>{% internal_link build.get_absolute_url build %}</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <th>{% trans "Part" %}</th> | ||||
|                 <td>{% internal_link part.get_absolute_url part.full_name %}</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <th>{% trans "Quantity" %}</th> | ||||
|                 <td>{{ build.quantity }}</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <th>{% trans "Description" %}</th> | ||||
|                 <td>{{ build.title }}</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <th>{% trans "Issued" %}</th> | ||||
|                 <td>{{ build.creation_date }}</td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <th>{% trans "Target Date" %}</th> | ||||
|                 <td> | ||||
|                     {% if build.target_date %} | ||||
|                     {{ build.target_date }} | ||||
|                     {% else %} | ||||
|                     <i>Not specified</i> | ||||
|                     {% endif %} | ||||
|                 </td> | ||||
|             </tr> | ||||
|             <tr> | ||||
|                 <th>{% trans "Sales Order" %}</th> | ||||
|                 <td> | ||||
|                     {% if build.sales_order %} | ||||
|                     {% internal_link build.sales_order.get_absolute_url build.sales_order %} | ||||
|                     {% else %} | ||||
|                     <i>Not specified</i> | ||||
|                     {% endif %} | ||||
|                 </td> | ||||
|             </tr> | ||||
|             {% if build.parent %} | ||||
|             <tr> | ||||
|                 <th>{% trans "Required For" %}</th> | ||||
|                 <td>{% internal_link build.parent.get_absolute_url build.parent %}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if build.issued_by %} | ||||
|             <tr> | ||||
|                 <th>{% trans "Issued By" %}</th> | ||||
|                 <td>{{ build.issued_by }}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if build.responsible %} | ||||
|             <tr> | ||||
|                 <th>{% trans "Responsible" %}</th> | ||||
|                 <td>{{ build.responsible }}</td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|             {% if build.link %} | ||||
|             <tr> | ||||
|                 <th>{% trans "Link" %}</th> | ||||
|                 <td><a href="{{ build.link }}">{{ build.link }}</a></td> | ||||
|             </tr> | ||||
|             {% endif %} | ||||
|         </table> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| <h3>{% trans "Notes" %}</h3> | ||||
|  | ||||
| {% if build.notes %} | ||||
| {{ build.notes|markdownify }} | ||||
| {% endif %} | ||||
|  | ||||
| {% endblock %} | ||||
		Reference in New Issue
	
	Block a user