# Reporting Module Implementation Plan with Excel Export ## Overview This document outlines the implementation plan for the reporting module, including Excel export functionality and various report types for the manufacturing application. ## Reporting Models ### 1. Report Template Model ```python # reports/models.py from django.db import models from django.contrib.auth.models import User class ReportTemplate(models.Model): REPORT_TYPES = [ ('inventory', 'Inventory Report'), ('sales', 'Sales Report'), ('purchasing', 'Purchasing Report'), ('manufacturing', 'Manufacturing Report'), ('financial', 'Financial Report'), ('custom', 'Custom Report'), ] name = models.CharField(max_length=100) description = models.TextField(blank=True) report_type = models.CharField(max_length=20, choices=REPORT_TYPES) template_file = models.FileField(upload_to='report_templates/', blank=True, null=True) is_active = models.BooleanField(default=True) created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name class GeneratedReport(models.Model): REPORT_FORMATS = [ ('pdf', 'PDF'), ('excel', 'Excel'), ('csv', 'CSV'), ('html', 'HTML'), ] report_template = models.ForeignKey(ReportTemplate, on_delete=models.CASCADE) name = models.CharField(max_length=200) description = models.TextField(blank=True) format = models.CharField(max_length=10, choices=REPORT_FORMATS) file_path = models.CharField(max_length=500, blank=True) file_size = models.BigIntegerField(default=0) generated_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) generated_at = models.DateTimeField(auto_now_add=True) parameters = models.JSONField(default=dict, blank=True, help_text="Parameters used to generate this report") def __str__(self): return self.name def get_absolute_url(self): from django.urls import reverse return reverse('reports:report_detail', kwargs={'report_id': self.id}) ``` ## Report Generation System ### 2. Report Generator Classes ```python # reports/generators.py import pandas as pd from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment from openpyxl.utils.dataframe import dataframe_to_rows from django.http import HttpResponse from django.template.loader import render_to_string import io import csv from datetime import datetime class BaseReportGenerator: """Base class for all report generators""" def __init__(self, report_template, parameters=None): self.report_template = report_template self.parameters = parameters or {} def generate_data(self): """Generate report data - to be implemented by subclasses""" raise NotImplementedError def generate_excel(self, data): """Generate Excel report""" df = pd.DataFrame(data) # Create Excel workbook wb = Workbook() ws = wb.active ws.title = self.report_template.name # Add data to worksheet for r in dataframe_to_rows(df, index=False, header=True): ws.append(r) # Style the header row header_font = Font(bold=True) header_fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid") for cell in ws[1]: cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal="center") # Auto-adjust column widths for column in ws.columns: max_length = 0 column_letter = column[0].column_letter for cell in column: try: if len(str(cell.value)) > max_length: max_length = len(str(cell.value)) except: pass adjusted_width = (max_length + 2) ws.column_dimensions[column_letter].width = min(adjusted_width, 50) # Save to bytes output = io.BytesIO() wb.save(output) output.seek(0) return output def generate_csv(self, data): """Generate CSV report""" df = pd.DataFrame(data) output = io.StringIO() df.to_csv(output, index=False) output.seek(0) return output def generate_html(self, data): """Generate HTML report""" context = { 'report_template': self.report_template, 'data': data, 'generated_at': datetime.now(), 'parameters': self.parameters, } html = render_to_string('reports/report_template.html', context) return io.StringIO(html) def generate_report(self, format='excel'): """Generate report in specified format""" data = self.generate_data() if format == 'excel': return self.generate_excel(data) elif format == 'csv': return self.generate_csv(data) elif format == 'html': return self.generate_html(data) else: raise ValueError(f"Unsupported format: {format}") class InventoryReportGenerator(BaseReportGenerator): """Generate inventory reports""" def generate_data(self): from inventory.models import Product, Inventory, Warehouse # Get inventory data inventory_data = [] inventories = Inventory.objects.select_related('product', 'warehouse').all() for inv in inventories: inventory_data.append({ 'Product Code': inv.product.code, 'Product Name': inv.product.name, 'Warehouse': inv.warehouse.name, 'Quantity': float(inv.quantity), 'Reserved Quantity': float(inv.reserved_quantity), 'Available Quantity': float(inv.available_quantity), 'Unit of Measure': inv.product.unit_of_measure.name if inv.product.unit_of_measure else '', 'Reorder Level': float(inv.product.reorder_level), 'Cost Price': float(inv.product.cost_price) if inv.product.cost_price else 0, 'Selling Price': float(inv.product.selling_price) if inv.product.selling_price else 0, }) return inventory_data class SalesReportGenerator(BaseReportGenerator): """Generate sales reports""" def generate_data(self): from sales.models import SalesOrder, SalesOrderItem, Customer # Get sales data based on parameters start_date = self.parameters.get('start_date') end_date = self.parameters.get('end_date') customer_id = self.parameters.get('customer_id') sales_orders = SalesOrder.objects.select_related('customer').prefetch_related('items__product') if start_date: sales_orders = sales_orders.filter(order_date__gte=start_date) if end_date: sales_orders = sales_orders.filter(order_date__lte=end_date) if customer_id: sales_orders = sales_orders.filter(customer_id=customer_id) # Generate sales data sales_data = [] for order in sales_orders: for item in order.items.all(): sales_data.append({ 'Order Number': order.so_number, 'Order Date': order.order_date.strftime('%Y-%m-%d'), 'Customer': order.customer.name, 'Product Code': item.product.code, 'Product Name': item.product.name, 'Quantity': float(item.quantity), 'Unit Price': float(item.unit_price), 'Total Price': float(item.total_price), 'Status': order.get_status_display(), }) return sales_data class PurchasingReportGenerator(BaseReportGenerator): """Generate purchasing reports""" def generate_data(self): from purchasing.models import PurchaseOrder, PurchaseOrderItem, Supplier # Get purchasing data based on parameters start_date = self.parameters.get('start_date') end_date = self.parameters.get('end_date') supplier_id = self.parameters.get('supplier_id') purchase_orders = PurchaseOrder.objects.select_related('supplier').prefetch_related('items__product') if start_date: purchase_orders = purchase_orders.filter(order_date__gte=start_date) if end_date: purchase_orders = purchase_orders.filter(order_date__lte=end_date) if supplier_id: purchase_orders = purchase_orders.filter(supplier_id=supplier_id) # Generate purchasing data purchasing_data = [] for order in purchase_orders: for item in order.items.all(): purchasing_data.append({ 'PO Number': order.po_number, 'Order Date': order.order_date.strftime('%Y-%m-%d'), 'Supplier': order.supplier.name, 'Product Code': item.product.code, 'Product Name': item.product.name, 'Quantity': float(item.quantity), 'Unit Price': float(item.unit_price), 'Total Price': float(item.total_price), 'Status': order.get_status_display(), }) return purchasing_data class ManufacturingReportGenerator(BaseReportGenerator): """Generate manufacturing reports""" def generate_data(self): from manufacturing.models import ManufacturingOrder, BillOfMaterial # Get manufacturing data based on parameters start_date = self.parameters.get('start_date') end_date = self.parameters.get('end_date') status = self.parameters.get('status') mo_queryset = ManufacturingOrder.objects.select_related('bom__product') if start_date: mo_queryset = mo_queryset.filter(scheduled_start_date__gte=start_date) if end_date: mo_queryset = mo_queryset.filter(scheduled_start_date__lte=end_date) if status: mo_queryset = mo_queryset.filter(status=status) # Generate manufacturing data manufacturing_data = [] for mo in mo_queryset: manufacturing_data.append({ 'MO Number': mo.mo_number, 'Product': mo.bom.product.name, 'Quantity to Produce': float(mo.quantity_to_produce), 'Scheduled Start': mo.scheduled_start_date.strftime('%Y-%m-%d'), 'Scheduled End': mo.scheduled_end_date.strftime('%Y-%m-%d'), 'Actual Start': mo.actual_start_date.strftime('%Y-%m-%d') if mo.actual_start_date else '', 'Actual End': mo.actual_end_date.strftime('%Y-%m-%d') if mo.actual_end_date else '', 'Status': mo.get_status_display(), }) return manufacturing_data class FinancialReportGenerator(BaseReportGenerator): """Generate financial reports""" def generate_data(self): from sales.models import SalesOrder from purchasing.models import PurchaseOrder # Get date range from parameters start_date = self.parameters.get('start_date') end_date = self.parameters.get('end_date') # Get sales data sales_queryset = SalesOrder.objects.all() if start_date: sales_queryset = sales_queryset.filter(order_date__gte=start_date) if end_date: sales_queryset = sales_queryset.filter(order_date__lte=end_date) total_sales = sales_queryset.aggregate( total=models.Sum('total_amount') )['total'] or 0 # Get purchasing data purchase_queryset = PurchaseOrder.objects.all() if start_date: purchase_queryset = purchase_queryset.filter(order_date__gte=start_date) if end_date: purchase_queryset = purchase_queryset.filter(order_date__lte=end_date) total_purchases = purchase_queryset.aggregate( total=models.Sum('total_amount') )['total'] or 0 # Generate financial summary financial_data = [{ 'Period': f"{start_date} to {end_date}" if start_date and end_date else 'All Time', 'Total Sales': float(total_sales), 'Total Purchases': float(total_purchases), 'Net Profit/Loss': float(total_sales - total_purchases), }] return financial_data # Factory for creating report generators class ReportGeneratorFactory: """Factory for creating report generators""" generators = { 'inventory': InventoryReportGenerator, 'sales': SalesReportGenerator, 'purchasing': PurchasingReportGenerator, 'manufacturing': ManufacturingReportGenerator, 'financial': FinancialReportGenerator, } @classmethod def create_generator(cls, report_type, report_template, parameters=None): generator_class = cls.generators.get(report_type) if not generator_class: raise ValueError(f"Unsupported report type: {report_type}") return generator_class(report_template, parameters) ``` ## Report Views ### 3. Report Views Implementation ```python # reports/views.py from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required, permission_required from django.http import HttpResponse, JsonResponse from django.contrib import messages from django.db.models import Q from .models import ReportTemplate, GeneratedReport from .generators import ReportGeneratorFactory from .forms import ReportParametersForm import os @login_required @permission_required('reports.view_report', raise_exception=True) def reports_dashboard(request): """Main reports dashboard view""" # Get recent reports recent_reports = GeneratedReport.objects.select_related( 'report_template', 'generated_by' ).order_by('-generated_at')[:10] # Get available report templates report_templates = ReportTemplate.objects.filter(is_active=True) context = { 'recent_reports': recent_reports, 'report_templates': report_templates, } return render(request, 'reports/dashboard.html', context) @login_required @permission_required('reports.view_report', raise_exception=True) def inventory_report_view(request): """Inventory report view""" template = get_object_or_404(ReportTemplate, report_type='inventory', is_active=True) if request.method == 'POST': # Generate report return generate_report_view(request, template.id) context = { 'report_template': template, 'report_type': 'inventory', } return render(request, 'reports/inventory_report.html', context) @login_required @permission_required('reports.view_report', raise_exception=True) def sales_report_view(request): """Sales report view""" template = get_object_or_404(ReportTemplate, report_type='sales', is_active=True) if request.method == 'POST': # Generate report return generate_report_view(request, template.id) context = { 'report_template': template, 'report_type': 'sales', } return render(request, 'reports/sales_report.html', context) @login_required @permission_required('reports.view_report', raise_exception=True) def purchasing_report_view(request): """Purchasing report view""" template = get_object_or_404(ReportTemplate, report_type='purchasing', is_active=True) if request.method == 'POST': # Generate report return generate_report_view(request, template.id) context = { 'report_template': template, 'report_type': 'purchasing', } return render(request, 'reports/purchasing_report.html', context) @login_required @permission_required('reports.view_report', raise_exception=True) def manufacturing_report_view(request): """Manufacturing report view""" template = get_object_or_404(ReportTemplate, report_type='manufacturing', is_active=True) if request.method == 'POST': # Generate report return generate_report_view(request, template.id) context = { 'report_template': template, 'report_type': 'manufacturing', } return render(request, 'reports/manufacturing_report.html', context) @login_required @permission_required('reports.view_report', raise_exception=True) def financial_report_view(request): """Financial report view""" template = get_object_or_404(ReportTemplate, report_type='financial', is_active=True) if request.method == 'POST': # Generate report return generate_report_view(request, template.id) context = { 'report_template': template, 'report_type': 'financial', } return render(request, 'reports/financial_report.html', context) @login_required @permission_required('reports.generate_report', raise_exception=True) def generate_report_view(request, template_id): """Generate a report""" template = get_object_or_404(ReportTemplate, id=template_id) # Get parameters from POST data parameters = {} for key, value in request.POST.items(): if key not in ['csrfmiddlewaretoken', 'format']: parameters[key] = value # Get format format = request.POST.get('format', 'excel') try: # Create report generator generator = ReportGeneratorFactory.create_generator( template.report_type, template, parameters ) # Generate report data output = generator.generate_report(format) # Create GeneratedReport record report_name = f"{template.name} - {datetime.now().strftime('%Y%m%d_%H%M%S')}" generated_report = GeneratedReport.objects.create( report_template=template, name=report_name, format=format, generated_by=request.user, parameters=parameters, ) # Return file response if format == 'excel': response = HttpResponse( output.getvalue(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = f'attachment; filename="{report_name}.xlsx"' elif format == 'csv': response = HttpResponse( output.getvalue(), content_type='text/csv' ) response['Content-Disposition'] = f'attachment; filename="{report_name}.csv"' else: response = HttpResponse( output.getvalue(), content_type='text/html' ) return response except Exception as e: messages.error(request, f"Error generating report: {str(e)}") return redirect('reports:dashboard') @login_required @permission_required('reports.view_report', raise_exception=True) def report_detail_view(request, report_id): """View details of a generated report""" report = get_object_or_404(GeneratedReport, id=report_id) context = { 'report': report, } return render(request, 'reports/report_detail.html', context) @login_required @permission_required('reports.export_report', raise_exception=True) def export_to_excel_view(request, report_id): """Export a report to Excel""" report = get_object_or_404(GeneratedReport, id=report_id) # Check if file exists if report.file_path and os.path.exists(report.file_path): with open(report.file_path, 'rb') as f: response = HttpResponse( f.read(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ) response['Content-Disposition'] = f'attachment; filename="{report.name}.xlsx"' return response else: messages.error(request, "Report file not found.") return redirect('reports:report_detail', report_id=report_id) @login_required @permission_required('reports.export_report', raise_exception=True) def export_to_pdf_view(request, report_id): """Export a report to PDF""" # Implementation would require a PDF generation library like WeasyPrint or ReportLab # This is a placeholder for future implementation messages.info(request, "PDF export is not yet implemented.") return redirect('reports:report_detail', report_id=report_id) ``` ## Report Forms ### 4. Report Parameter Forms ```python # reports/forms.py from django import forms from django.forms.widgets import DateInput from inventory.models import Product, Warehouse from sales.models import Customer from purchasing.models import Supplier class BaseReportForm(forms.Form): """Base form for report parameters""" start_date = forms.DateField( required=False, widget=DateInput(attrs={'type': 'date', 'class': 'form-control'}), label='Start Date' ) end_date = forms.DateField( required=False, widget=DateInput(attrs={'type': 'date', 'class': 'form-control'}), label='End Date' ) class InventoryReportForm(BaseReportForm): """Form for inventory report parameters""" product = forms.ModelChoiceField( queryset=Product.objects.all(), required=False, empty_label="All Products", widget=forms.Select(attrs={'class': 'form-control'}) ) warehouse = forms.ModelChoiceField( queryset=Warehouse.objects.all(), required=False, empty_label="All Warehouses", widget=forms.Select(attrs={'class': 'form-control'}) ) class SalesReportForm(BaseReportForm): """Form for sales report parameters""" customer = forms.ModelChoiceField( queryset=Customer.objects.all(), required=False, empty_label="All Customers", widget=forms.Select(attrs={'class': 'form-control'}) ) product = forms.ModelChoiceField( queryset=Product.objects.all(), required=False, empty_label="All Products", widget=forms.Select(attrs={'class': 'form-control'}) ) class PurchasingReportForm(BaseReportForm): """Form for purchasing report parameters""" supplier = forms.ModelChoiceField( queryset=Supplier.objects.all(), required=False, empty_label="All Suppliers", widget=forms.Select(attrs={'class': 'form-control'}) ) product = forms.ModelChoiceField( queryset=Product.objects.all(), required=False, empty_label="All Products", widget=forms.Select(attrs={'class': 'form-control'}) ) class ManufacturingReportForm(BaseReportForm): """Form for manufacturing report parameters""" product = forms.ModelChoiceField( queryset=Product.objects.all(), required=False, empty_label="All Products", widget=forms.Select(attrs={'class': 'form-control'}) ) status = forms.ChoiceField( choices=[ ('', 'All Statuses'), ('draft', 'Draft'), ('planned', 'Planned'), ('in_progress', 'In Progress'), ('completed', 'Completed'), ('cancelled', 'Cancelled'), ], required=False, widget=forms.Select(attrs={'class': 'form-control'}) ) class FinancialReportForm(BaseReportForm): """Form for financial report parameters""" pass # Uses base form fields ``` ## Report Templates ### 5. Report Dashboard Template ```html {% extends 'base.html' %} {% block title %}Reports - Manufacturing App{% endblock %} {% block content %}

Reports

Report Types
Inventory Report

Detailed inventory status and stock levels

Generate Report
Sales Report

Sales orders, revenue, and customer analysis

Generate Report
Purchasing Report

Purchase orders and supplier analysis

Generate Report
Manufacturing Report

Production orders and manufacturing analysis

Generate Report
Financial Report

Financial summary and profit/loss analysis

Generate Report
Recent Reports
{% if recent_reports %} {% else %}

No recent reports generated.

{% endif %}
{% endblock %} ``` ### 6. Report Generation Template ```html {% extends 'base.html' %} {% block title %}Generate {{ report_template.name }} - Manufacturing App{% endblock %} {% block content %}

Generate {{ report_template.name }}

Report Parameters
{% csrf_token %} {% if form.non_field_errors %}
{{ form.non_field_errors }}
{% endif %}
{{ form.start_date }}
{{ form.end_date }}
{% for field in form %} {% if field.name not in ['start_date', 'end_date'] %}
{{ field }}
{% endif %} {% endfor %}
Report Information
{{ report_template.name }}

{{ report_template.description }}


Instructions
  • Select the date range for your report
  • Choose any additional filters
  • Select your preferred export format
  • Click "Generate Report" to download
{% endblock %} ``` ## Report URLs ### 7. Report URL Configuration ```python # reports/urls.py from django.urls import path from . import views app_name = 'reports' urlpatterns = [ path('', views.reports_dashboard, name='dashboard'), path('inventory/', views.inventory_report_view, name='inventory_report'), path('sales/', views.sales_report_view, name='sales_report'), path('purchasing/', views.purchasing_report_view, name='purchasing_report'), path('manufacturing/', views.manufacturing_report_view, name='manufacturing_report'), path('financial/', views.financial_report_view, name='financial_report'), path('/', views.report_detail_view, name='report_detail'), path('/export/excel/', views.export_to_excel_view, name='export_excel'), path('/export/pdf/', views.export_to_pdf_view, name='export_pdf'), path('generate//', views.generate_report_view, name='generate_report'), ] ``` This reporting module implementation plan provides a comprehensive solution for generating various types of reports with Excel export capability. The modular design allows for easy extension to support additional report types and export formats in the future.