945 lines
35 KiB
Markdown
945 lines
35 KiB
Markdown
# 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
|
|
<!-- templates/reports/dashboard.html -->
|
|
|
|
{% extends 'base.html' %}
|
|
|
|
{% block title %}Reports - Manufacturing App{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Reports</h1>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Report Types</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-boxes fa-2x mb-2 text-primary"></i>
|
|
<h6 class="card-title">Inventory Report</h6>
|
|
<p class="card-text">Detailed inventory status and stock levels</p>
|
|
<a href="{% url 'reports:inventory_report' %}" class="btn btn-primary btn-sm">
|
|
Generate Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-dollar-sign fa-2x mb-2 text-success"></i>
|
|
<h6 class="card-title">Sales Report</h6>
|
|
<p class="card-text">Sales orders, revenue, and customer analysis</p>
|
|
<a href="{% url 'reports:sales_report' %}" class="btn btn-success btn-sm">
|
|
Generate Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-shopping-cart fa-2x mb-2 text-info"></i>
|
|
<h6 class="card-title">Purchasing Report</h6>
|
|
<p class="card-text">Purchase orders and supplier analysis</p>
|
|
<a href="{% url 'reports:purchasing_report' %}" class="btn btn-info btn-sm">
|
|
Generate Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-cogs fa-2x mb-2 text-warning"></i>
|
|
<h6 class="card-title">Manufacturing Report</h6>
|
|
<p class="card-text">Production orders and manufacturing analysis</p>
|
|
<a href="{% url 'reports:manufacturing_report' %}" class="btn btn-warning btn-sm">
|
|
Generate Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-chart-line fa-2x mb-2 text-danger"></i>
|
|
<h6 class="card-title">Financial Report</h6>
|
|
<p class="card-text">Financial summary and profit/loss analysis</p>
|
|
<a href="{% url 'reports:financial_report' %}" class="btn btn-danger btn-sm">
|
|
Generate Report
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Recent Reports</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if recent_reports %}
|
|
<div class="list-group">
|
|
{% for report in recent_reports %}
|
|
<a href="{% url 'reports:report_detail' report.id %}"
|
|
class="list-group-item list-group-item-action">
|
|
<div class="d-flex w-100 justify-content-between">
|
|
<h6 class="mb-1">{{ report.name }}</h6>
|
|
<small>{{ report.generated_at|date:"d M Y H:i" }}</small>
|
|
</div>
|
|
<small>{{ report.report_template.name }} ({{ report.get_format_display }})</small>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<p class="text-muted">No recent reports generated.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
```
|
|
|
|
### 6. Report Generation Template
|
|
```html
|
|
<!-- templates/reports/generate_report.html -->
|
|
|
|
{% extends 'base.html' %}
|
|
|
|
{% block title %}Generate {{ report_template.name }} - Manufacturing App{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Generate {{ report_template.name }}</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<a href="{% url 'reports:dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
|
<i class="fas fa-arrow-left"></i> Back to Reports
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Report Parameters</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
|
|
{% if form.non_field_errors %}
|
|
<div class="alert alert-danger">
|
|
{{ form.non_field_errors }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.start_date.id_for_label }}" class="form-label">
|
|
{{ form.start_date.label }}
|
|
</label>
|
|
{{ form.start_date }}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="{{ form.end_date.id_for_label }}" class="form-label">
|
|
{{ form.end_date.label }}
|
|
</label>
|
|
{{ form.end_date }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional form fields based on report type -->
|
|
{% for field in form %}
|
|
{% if field.name not in ['start_date', 'end_date'] %}
|
|
<div class="mb-3">
|
|
<label for="{{ field.id_for_label }}" class="form-label">
|
|
{{ field.label }}
|
|
</label>
|
|
{{ field }}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label">Export Format</label>
|
|
<div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="format"
|
|
id="formatExcel" value="excel" checked>
|
|
<label class="form-check-label" for="formatExcel">
|
|
<i class="fas fa-file-excel text-success"></i> Excel
|
|
</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="format"
|
|
id="formatCsv" value="csv">
|
|
<label class="form-check-label" for="formatCsv">
|
|
<i class="fas fa-file-csv text-info"></i> CSV
|
|
</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="radio" name="format"
|
|
id="formatPdf" value="pdf" disabled>
|
|
<label class="form-check-label" for="formatPdf">
|
|
<i class="fas fa-file-pdf text-danger"></i> PDF (Coming Soon)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-file-export"></i> Generate Report
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Report Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<h6>{{ report_template.name }}</h6>
|
|
<p>{{ report_template.description }}</p>
|
|
|
|
<hr>
|
|
|
|
<h6>Instructions</h6>
|
|
<ul>
|
|
<li>Select the date range for your report</li>
|
|
<li>Choose any additional filters</li>
|
|
<li>Select your preferred export format</li>
|
|
<li>Click "Generate Report" to download</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% 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('<int:report_id>/', views.report_detail_view, name='report_detail'),
|
|
path('<int:report_id>/export/excel/', views.export_to_excel_view, name='export_excel'),
|
|
path('<int:report_id>/export/pdf/', views.export_to_pdf_view, name='export_pdf'),
|
|
path('generate/<int:template_id>/', 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. |