Django_Basic_Manufacturing_3/apps/reports/utils.py
2025-08-22 17:05:22 +07:00

549 lines
21 KiB
Python

import pandas as pd
from io import BytesIO
from django.http import HttpResponse
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from openpyxl import Workbook
from openpyxl.styles import Font, Border, Side, Alignment, PatternFill
from decimal import Decimal
import locale
def format_currency(amount):
"""Format amount as Indonesian Rupiah"""
if amount is None:
return "Rp 0,00"
try:
# Convert to Decimal for precise handling
if not isinstance(amount, Decimal):
amount = Decimal(str(amount))
# Format with Indonesian decimal separator (comma)
formatted = f"{amount:,.2f}"
# Convert to Indonesian format: comma to dot, dot to comma
formatted = formatted.replace(',', 'temp_comma').replace('.', ',').replace('temp_comma', '.')
return f"Rp {formatted}"
except (ValueError, TypeError):
return "Rp 0,00"
def export_inventory_to_excel(inventory_items, total_inventory_value):
"""Export inventory data to Excel"""
wb = Workbook()
ws = wb.active
ws.title = "Inventory Report"
# Header
ws['A1'] = "INVENTORY REPORT"
ws['A1'].font = Font(bold=True, size=14)
ws.merge_cells('A1:I1')
# Column headers
headers = [
'Product Code', 'Product Name', 'Category', 'Warehouse',
'Quantity', 'Unit', 'Unit Cost', 'Total Value', 'Status'
]
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=3, column=col_num)
cell.value = header
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
cell.alignment = Alignment(horizontal='center')
# Data rows
for row_num, item in enumerate(inventory_items, 4):
ws.cell(row=row_num, column=1).value = item.product.code
ws.cell(row=row_num, column=2).value = item.product.name
ws.cell(row=row_num, column=3).value = item.product.category.name
ws.cell(row=row_num, column=4).value = item.warehouse.name
ws.cell(row=row_num, column=5).value = float(item.quantity)
ws.cell(row=row_num, column=6).value = item.product.unit_of_measure.abbreviation
ws.cell(row=row_num, column=7).value = float(item.product.cost_price)
ws.cell(row=row_num, column=8).value = float(item.total_value)
ws.cell(row=row_num, column=9).value = 'In Stock' if item.quantity > item.product.reorder_level else 'Low Stock' if item.quantity > 0 else 'Out of Stock'
# Add borders to data cells
for col_num in range(1, 10):
cell = ws.cell(row=row_num, column=col_num)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# Total row
total_row = len(inventory_items) + 5
ws.cell(row=total_row, column=7).value = "TOTAL VALUE:"
ws.cell(row=total_row, column=7).font = Font(bold=True)
ws.cell(row=total_row, column=8).value = float(total_inventory_value)
ws.cell(row=total_row, column=8).font = Font(bold=True)
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_cells = [cell for cell in column]
for cell in column_cells:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
adjusted_width = (max_length + 2)
# Get the column letter from the first cell that is not a merged cell
column_letter = None
for cell in column_cells:
if hasattr(cell, 'column_letter'):
column_letter = cell.column_letter
break
if column_letter:
ws.column_dimensions[column_letter].width = min(adjusted_width, 50)
# Create response
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=inventory_report.xlsx'
wb.save(response)
return response
def export_inventory_to_pdf(inventory_items, total_inventory_value):
"""Export inventory data to PDF"""
buffer = BytesIO()
doc = SimpleDocTemplate(buffer, pagesize=A4)
elements = []
styles = getSampleStyleSheet()
# Title
title_style = styles['Heading1']
title = Paragraph("INVENTORY REPORT", title_style)
elements.append(title)
elements.append(Spacer(1, 20))
# Table data
data = [['Product Code', 'Product Name', 'Category', 'Warehouse',
'Quantity', 'Unit', 'Unit Cost', 'Total Value', 'Status']]
for item in inventory_items:
data.append([
item.product.code,
item.product.name,
item.product.category.name,
item.warehouse.name,
str(item.quantity),
item.product.unit_of_measure.abbreviation,
format_currency(item.product.cost_price),
format_currency(item.total_value),
'In Stock' if item.quantity > item.product.reorder_level else 'Low Stock' if item.quantity > 0 else 'Out of Stock'
])
# Add total row
data.append(['', '', '', '', '', '', 'TOTAL VALUE:', format_currency(total_inventory_value), ''])
# Create table
table = Table(data)
table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 12),
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
('BACKGROUND', (0, -1), (-1, -1), colors.lightgrey),
('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, colors.black)
]))
elements.append(table)
doc.build(elements)
buffer.seek(0)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename=inventory_report.pdf'
response.write(buffer.getvalue())
buffer.close()
return response
def export_sales_to_excel(sales_orders, total_revenue, avg_order_value, total_orders):
"""Export sales data to Excel"""
wb = Workbook()
ws = wb.active
ws.title = "Sales Report"
# Header
ws['A1'] = "SALES REPORT"
ws['A1'].font = Font(bold=True, size=14)
ws.merge_cells('A1:H1')
# Column headers
headers = [
'Order Number', 'Customer', 'Order Date', 'Expected Delivery',
'Status', 'Items', 'Total Amount'
]
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=3, column=col_num)
cell.value = header
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
cell.alignment = Alignment(horizontal='center')
# Data rows
for row_num, order in enumerate(sales_orders, 4):
ws.cell(row=row_num, column=1).value = order.so_number
ws.cell(row=row_num, column=2).value = order.customer.name
ws.cell(row=row_num, column=3).value = str(order.order_date)
ws.cell(row=row_num, column=4).value = str(order.expected_delivery_date) if order.expected_delivery_date else "Not set"
ws.cell(row=row_num, column=5).value = order.get_status_display()
ws.cell(row=row_num, column=6).value = order.items.count()
ws.cell(row=row_num, column=7).value = float(order.total_amount)
# Add borders to data cells
for col_num in range(1, 8):
cell = ws.cell(row=row_num, column=col_num)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# Summary section
summary_row = len(sales_orders) + 6
ws.cell(row=summary_row, column=5).value = "TOTAL ORDERS:"
ws.cell(row=summary_row, column=5).font = Font(bold=True)
ws.cell(row=summary_row, column=6).value = total_orders
summary_row += 1
ws.cell(row=summary_row, column=5).value = "TOTAL REVENUE:"
ws.cell(row=summary_row, column=5).font = Font(bold=True)
ws.cell(row=summary_row, column=6).value = float(total_revenue)
summary_row += 1
ws.cell(row=summary_row, column=5).value = "AVG ORDER VALUE:"
ws.cell(row=summary_row, column=5).font = Font(bold=True)
ws.cell(row=summary_row, column=6).value = float(avg_order_value)
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_cells = [cell for cell in column]
for cell in column_cells:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
adjusted_width = (max_length + 2)
# Get the column letter from the first cell that is not a merged cell
column_letter = None
for cell in column_cells:
if hasattr(cell, 'column_letter'):
column_letter = cell.column_letter
break
if column_letter:
ws.column_dimensions[column_letter].width = min(adjusted_width, 50)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=sales_report.xlsx'
wb.save(response)
return response
def export_manufacturing_to_excel(manufacturing_orders, total_orders, completed_orders, total_boms):
"""Export manufacturing data to Excel"""
wb = Workbook()
ws = wb.active
ws.title = "Manufacturing Report"
# Header
ws['A1'] = "MANUFACTURING REPORT"
ws['A1'].font = Font(bold=True, size=14)
ws.merge_cells('A1:H1')
# Column headers
headers = [
'MO Number', 'BOM', 'Product', 'Quantity', 'Status',
'Start Date', 'End Date', 'Total Cost'
]
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=3, column=col_num)
cell.value = header
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
cell.alignment = Alignment(horizontal='center')
# Data rows
for row_num, order in enumerate(manufacturing_orders, 4):
ws.cell(row=row_num, column=1).value = order.mo_number
ws.cell(row=row_num, column=2).value = order.bom.bom_code
ws.cell(row=row_num, column=3).value = order.bom.product.name
ws.cell(row=row_num, column=4).value = float(order.quantity_to_produce)
ws.cell(row=row_num, column=5).value = order.get_status_display()
ws.cell(row=row_num, column=6).value = str(order.actual_start_date) if order.actual_start_date else "Not started"
ws.cell(row=row_num, column=7).value = str(order.actual_end_date) if order.actual_end_date else "Not completed"
ws.cell(row=row_num, column=8).value = float(order.total_cost) if order.total_cost else 0
# Add borders to data cells
for col_num in range(1, 9):
cell = ws.cell(row=row_num, column=col_num)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# Summary section
summary_row = len(manufacturing_orders) + 6
ws.cell(row=summary_row, column=6).value = "TOTAL ORDERS:"
ws.cell(row=summary_row, column=6).font = Font(bold=True)
ws.cell(row=summary_row, column=7).value = total_orders
summary_row += 1
ws.cell(row=summary_row, column=6).value = "COMPLETED:"
ws.cell(row=summary_row, column=6).font = Font(bold=True)
ws.cell(row=summary_row, column=7).value = completed_orders
summary_row += 1
ws.cell(row=summary_row, column=6).value = "TOTAL BOMS:"
ws.cell(row=summary_row, column=6).font = Font(bold=True)
ws.cell(row=summary_row, column=7).value = total_boms
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_cells = [cell for cell in column]
for cell in column_cells:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
adjusted_width = (max_length + 2)
# Get the column letter from the first cell that is not a merged cell
column_letter = None
for cell in column_cells:
if hasattr(cell, 'column_letter'):
column_letter = cell.column_letter
break
if column_letter:
ws.column_dimensions[column_letter].width = min(adjusted_width, 50)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=manufacturing_report.xlsx'
wb.save(response)
return response
def export_purchasing_to_excel(purchase_orders, total_cost, avg_order_value, total_orders):
"""Export purchasing data to Excel"""
wb = Workbook()
ws = wb.active
ws.title = "Purchasing Report"
# Header
ws['A1'] = "PURCHASING REPORT"
ws['A1'].font = Font(bold=True, size=14)
ws.merge_cells('A1:H1')
# Column headers
headers = [
'PO Number', 'Supplier', 'Order Date', 'Expected Delivery',
'Status', 'Items', 'Total Amount'
]
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=3, column=col_num)
cell.value = header
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
cell.alignment = Alignment(horizontal='center')
# Data rows
for row_num, order in enumerate(purchase_orders, 4):
ws.cell(row=row_num, column=1).value = order.po_number
ws.cell(row=row_num, column=2).value = order.supplier.name
ws.cell(row=row_num, column=3).value = str(order.order_date)
ws.cell(row=row_num, column=4).value = str(order.expected_delivery_date) if order.expected_delivery_date else "Not set"
ws.cell(row=row_num, column=5).value = order.get_status_display()
ws.cell(row=row_num, column=6).value = order.items.count()
ws.cell(row=row_num, column=7).value = float(order.total_amount)
# Add borders to data cells
for col_num in range(1, 8):
cell = ws.cell(row=row_num, column=col_num)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# Summary section
summary_row = len(purchase_orders) + 6
ws.cell(row=summary_row, column=5).value = "TOTAL ORDERS:"
ws.cell(row=summary_row, column=5).font = Font(bold=True)
ws.cell(row=summary_row, column=6).value = total_orders
summary_row += 1
ws.cell(row=summary_row, column=5).value = "TOTAL COST:"
ws.cell(row=summary_row, column=5).font = Font(bold=True)
ws.cell(row=summary_row, column=6).value = float(total_cost)
summary_row += 1
ws.cell(row=summary_row, column=5).value = "AVG ORDER VALUE:"
ws.cell(row=summary_row, column=5).font = Font(bold=True)
ws.cell(row=summary_row, column=6).value = float(avg_order_value)
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_cells = [cell for cell in column]
for cell in column_cells:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
adjusted_width = (max_length + 2)
# Get the column letter from the first cell that is not a merged cell
column_letter = None
for cell in column_cells:
if hasattr(cell, 'column_letter'):
column_letter = cell.column_letter
break
if column_letter:
ws.column_dimensions[column_letter].width = min(adjusted_width, 50)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=purchasing_report.xlsx'
wb.save(response)
return response
def export_financial_to_excel(total_revenue, total_purchase_cost, total_manufacturing_cost, total_inventory_value, gross_profit, profit_margin, monthly_trends):
"""Export financial data to Excel"""
wb = Workbook()
ws = wb.active
ws.title = "Financial Report"
# Header
ws['A1'] = "FINANCIAL REPORT"
ws['A1'].font = Font(bold=True, size=14)
ws.merge_cells('A1:F1')
# Financial Overview
ws['A3'] = "Financial Overview"
ws['A3'].font = Font(bold=True, size=12)
# Overview data
overview_data = [
['Metric', 'Value'],
['Total Revenue', total_revenue],
['Total Purchase Cost', total_purchase_cost],
['Total Manufacturing Cost', total_manufacturing_cost],
['Total Inventory Value', total_inventory_value],
['Gross Profit', gross_profit],
['Profit Margin (%)', profit_margin]
]
for row_num, row_data in enumerate(overview_data, 3):
for col_num, cell_data in enumerate(row_data, 1):
cell = ws.cell(row=row_num, column=col_num)
cell.value = cell_data
if row_num == 3 or col_num == 1: # Header or first column
cell.font = Font(bold=True)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# Monthly Trends Header
trend_start_row = len(overview_data) + 5
ws.cell(row=trend_start_row, column=1).value = "Monthly Trends"
ws.cell(row=trend_start_row, column=1).font = Font(bold=True, size=12)
# Monthly Trends data
trend_headers = ['Period', 'Revenue', 'Costs', 'Profit']
for col_num, header in enumerate(trend_headers, 1):
cell = ws.cell(row=trend_start_row + 1, column=col_num)
cell.value = header
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
cell.alignment = Alignment(horizontal='center')
# Monthly trends data rows
for row_num, trend in enumerate(monthly_trends, trend_start_row + 2):
ws.cell(row=row_num, column=1).value = trend['period']
ws.cell(row=row_num, column=2).value = float(trend['revenue'])
ws.cell(row=row_num, column=3).value = float(trend['costs'])
ws.cell(row=row_num, column=4).value = float(trend['profit'])
# Add borders to data cells
for col_num in range(1, 5):
cell = ws.cell(row=row_num, column=col_num)
cell.border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# Auto-adjust column widths
for column in ws.columns:
max_length = 0
column_cells = [cell for cell in column]
for cell in column_cells:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
adjusted_width = (max_length + 2)
# Get the column letter from the first cell that is not a merged cell
column_letter = None
for cell in column_cells:
if hasattr(cell, 'column_letter'):
column_letter = cell.column_letter
break
if column_letter:
ws.column_dimensions[column_letter].width = min(adjusted_width, 50)
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename=financial_report.xlsx'
wb.save(response)
return response