Django_Basic_Manufacturing_3/apps/manufacturing/views.py
2025-08-22 17:05:22 +07:00

442 lines
15 KiB
Python

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.http import HttpResponse
from django.db.models import Count, Q, Sum
from django.utils import timezone
from datetime import timedelta
from django import forms
from django.forms import inlineformset_factory
from decimal import Decimal
from .models import BillOfMaterial, BOMItem, ManufacturingOrder, MOComponent
from apps.inventory.models import Product
class BOMItemForm(forms.ModelForm):
class Meta:
model = BOMItem
fields = ['component', 'quantity', 'unit_of_measure']
widgets = {
'quantity': forms.NumberInput(attrs={'step': '0.01', 'min': '0'}),
}
class BOMForm(forms.ModelForm):
class Meta:
model = BillOfMaterial
fields = ['product', 'version', 'is_active']
widgets = {
'version': forms.TextInput(attrs={'placeholder': '1.0'}),
}
BOMItemFormSet = inlineformset_factory(
BillOfMaterial,
BOMItem,
form=BOMItemForm,
extra=1,
can_delete=True,
min_num=1,
validate_min=True
)
class MOComponentForm(forms.ModelForm):
class Meta:
model = MOComponent
fields = ['component', 'required_quantity']
widgets = {
'required_quantity': forms.NumberInput(attrs={'step': '0.01', 'min': '0'}),
}
class MOForm(forms.ModelForm):
class Meta:
model = ManufacturingOrder
fields = ['mo_number', 'bom', 'quantity_to_produce', 'scheduled_start_date', 'scheduled_end_date']
widgets = {
'scheduled_start_date': forms.DateInput(attrs={'type': 'date'}),
'scheduled_end_date': forms.DateInput(attrs={'type': 'date'}),
'quantity_to_produce': forms.NumberInput(attrs={'step': '0.01', 'min': '0'}),
}
MOComponentFormSet = inlineformset_factory(
ManufacturingOrder,
MOComponent,
form=MOComponentForm,
extra=0,
can_delete=True,
min_num=0
)
@login_required
def manufacturing_dashboard(request):
"""Manufacturing dashboard view"""
total_boms = BillOfMaterial.objects.filter(is_active=True)
active_orders = ManufacturingOrder.objects.filter(
Q(status='in_progress') | Q(status='scheduled')
)
completed_orders = ManufacturingOrder.objects.filter(
status='completed',
actual_end_date__gte=timezone.now() - timedelta(days=30)
)
recent_orders = ManufacturingOrder.objects.select_related('bom__product').order_by('-created_at')[:5]
context = {
'module_title': 'Manufacturing Management',
'total_boms': total_boms,
'active_orders': active_orders,
'completed_orders': completed_orders,
'recent_orders': recent_orders,
}
return render(request, 'manufacturing/dashboard.html', context)
# Bill of Material Views
@login_required
@permission_required('manufacturing.view_billofmaterial', raise_exception=True)
def bom_list_view(request):
"""List all bills of materials"""
boms = BillOfMaterial.objects.all()
context = {
'module_title': 'Bill of Materials List',
'boms': boms,
}
return render(request, 'manufacturing/bom_list.html', context)
@login_required
@permission_required('manufacturing.add_billofmaterial', raise_exception=True)
def create_bom_view(request):
"""Create a new bill of material"""
if request.method == 'POST':
form = BOMForm(request.POST)
formset = BOMItemFormSet(request.POST)
if form.is_valid() and formset.is_valid():
# Save the BOM
bom = form.save(commit=False)
bom.save()
# Save the BOM items
items = formset.save(commit=False)
for item in items:
item.bom = bom
item.save()
messages.success(request, f'Bill of Material "{bom.bom_code}" created successfully!')
return redirect('manufacturing:bom_detail', bom_id=bom.id)
else:
form = BOMForm()
formset = BOMItemFormSet()
context = {
'form': form,
'formset': formset,
'module_title': 'Create Bill of Material',
'is_create': True,
}
return render(request, 'manufacturing/bom_form.html', context)
@login_required
@permission_required('manufacturing.view_billofmaterial', raise_exception=True)
def bom_detail_view(request, bom_id):
"""View bill of material details"""
try:
bom = BillOfMaterial.objects.get(id=bom_id)
context = {
'module_title': 'Bill of Material Details',
'bom': bom,
}
return render(request, 'manufacturing/bom_detail.html', context)
except BillOfMaterial.DoesNotExist:
messages.error(request, 'Bill of material not found')
return redirect('manufacturing:bom_list')
@login_required
@permission_required('manufacturing.change_billofmaterial', raise_exception=True)
def edit_bom_view(request, bom_id):
"""Edit an existing bill of material"""
try:
bom = get_object_or_404(BillOfMaterial, id=bom_id)
except:
messages.error(request, 'Bill of Material not found.')
return redirect('manufacturing:bom_list')
if request.method == 'POST':
form = BOMForm(request.POST, instance=bom)
formset = BOMItemFormSet(request.POST, instance=bom)
if form.is_valid() and formset.is_valid():
# Save the BOM
bom = form.save(commit=False)
bom.save()
# Delete existing items and save new ones
BOMItem.objects.filter(bom=bom).delete()
items = formset.save(commit=False)
for item in items:
item.bom = bom
item.save()
messages.success(request, f'Bill of Material "{bom.bom_code}" updated successfully!')
return redirect('manufacturing:bom_detail', bom_id=bom.id)
else:
form = BOMForm(instance=bom)
formset = BOMItemFormSet(instance=bom)
context = {
'form': form,
'formset': formset,
'bom': bom,
'module_title': f'Edit Bill of Material: {bom.bom_code}',
'is_create': False,
}
return render(request, 'manufacturing/bom_form.html', context)
@login_required
@permission_required('manufacturing.delete_billofmaterial', raise_exception=True)
def delete_bom_view(request, bom_id):
"""Delete a bill of material"""
try:
bom = get_object_or_404(BillOfMaterial, id=bom_id)
except:
messages.error(request, 'Bill of Material not found.')
return redirect('manufacturing:bom_list')
if request.method == 'POST':
bom_code = bom.bom_code
bom.delete()
messages.success(request, f'Bill of Material "{bom_code}" deleted successfully!')
return redirect('manufacturing:bom_list')
context = {
'bom': bom,
'module_title': f'Delete Bill of Material: {bom.bom_code}',
}
return render(request, 'manufacturing/bom_confirm_delete.html', context)
# Manufacturing Order Views
@login_required
@permission_required('manufacturing.view_manufacturingorder', raise_exception=True)
def mo_list_view(request):
"""List all manufacturing orders"""
mos = ManufacturingOrder.objects.all()
context = {
'module_title': 'Manufacturing Order List',
'mos': mos,
}
return render(request, 'manufacturing/mo_list.html', context)
@login_required
@permission_required('manufacturing.add_manufacturingorder', raise_exception=True)
def create_mo_view(request):
"""Create a new manufacturing order"""
if request.method == 'POST':
form = MOForm(request.POST)
formset = MOComponentFormSet(request.POST)
if form.is_valid() and formset.is_valid():
# Save the MO
mo = form.save(commit=False)
mo.save()
# Save the MO components
components = formset.save(commit=False)
for component in components:
component.manufacturing_order = mo
component.save()
messages.success(request, f'Manufacturing Order "{mo.mo_number}" created successfully!')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
else:
form = MOForm()
formset = MOComponentFormSet()
context = {
'form': form,
'formset': formset,
'module_title': 'Create Manufacturing Order',
'is_create': True,
}
return render(request, 'manufacturing/mo_form.html', context)
@login_required
@permission_required('manufacturing.view_manufacturingorder', raise_exception=True)
def mo_detail_view(request, mo_number):
"""View manufacturing order details"""
try:
mo = ManufacturingOrder.objects.get(mo_number=mo_number)
context = {
'module_title': 'Manufacturing Order Details',
'mo': mo,
}
return render(request, 'manufacturing/mo_detail.html', context)
except ManufacturingOrder.DoesNotExist:
messages.error(request, 'Manufacturing order not found')
return redirect('manufacturing:mo_list')
@login_required
@permission_required('manufacturing.change_manufacturingorder', raise_exception=True)
def edit_mo_view(request, mo_number):
"""Edit an existing manufacturing order"""
try:
mo = get_object_or_404(ManufacturingOrder, mo_number=mo_number)
except:
messages.error(request, 'Manufacturing Order not found.')
return redirect('manufacturing:mo_list')
if request.method == 'POST':
form = MOForm(request.POST, instance=mo)
formset = MOComponentFormSet(request.POST, instance=mo)
if form.is_valid() and formset.is_valid():
# Save the MO
mo = form.save(commit=False)
mo.save()
# Delete existing components and save new ones
MOComponent.objects.filter(manufacturing_order=mo).delete()
components = formset.save(commit=False)
for component in components:
component.manufacturing_order = mo
component.save()
messages.success(request, f'Manufacturing Order "{mo.mo_number}" updated successfully!')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
else:
form = MOForm(instance=mo)
formset = MOComponentFormSet(instance=mo)
context = {
'form': form,
'formset': formset,
'mo': mo,
'module_title': f'Edit Manufacturing Order: {mo.mo_number}',
'is_create': False,
}
return render(request, 'manufacturing/mo_form.html', context)
@login_required
@permission_required('manufacturing.change_manufacturingorder', raise_exception=True)
def start_mo_view(request, mo_number):
"""Start a manufacturing order"""
try:
mo = get_object_or_404(ManufacturingOrder, mo_number=mo_number)
except:
messages.error(request, 'Manufacturing Order not found.')
return redirect('manufacturing:mo_list')
if mo.status != 'scheduled':
messages.error(request, f'Cannot start Manufacturing Order in {mo.get_status_display()} status.')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
if request.method == 'POST':
mo.status = 'in_progress'
mo.actual_start_date = timezone.now()
mo.save()
messages.success(request, f'Manufacturing Order "{mo.mo_number}" started successfully!')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
context = {
'mo': mo,
'module_title': f'Start Manufacturing Order: {mo.mo_number}',
'action': 'Start',
}
return render(request, 'manufacturing/mo_confirm_action.html', context)
@login_required
@permission_required('manufacturing.change_manufacturingorder', raise_exception=True)
def complete_mo_view(request, mo_number):
"""Complete a manufacturing order"""
try:
mo = get_object_or_404(ManufacturingOrder, mo_number=mo_number)
except:
messages.error(request, 'Manufacturing Order not found.')
return redirect('manufacturing:mo_list')
if mo.status != 'in_progress':
messages.error(request, f'Cannot complete Manufacturing Order in {mo.get_status_display()} status.')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
if request.method == 'POST':
mo.status = 'completed'
mo.actual_end_date = timezone.now().date() # Using date() since actual_end_date is DateField
mo.save()
messages.success(request, f'Manufacturing Order "{mo.mo_number}" completed successfully!')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
context = {
'mo': mo,
'module_title': f'Complete Manufacturing Order: {mo.mo_number}',
'action': 'Complete',
}
return render(request, 'manufacturing/mo_confirm_action.html', context)
@login_required
@permission_required('manufacturing.change_manufacturingorder', raise_exception=True)
def cancel_mo_view(request, mo_number):
"""Cancel a manufacturing order"""
try:
mo = get_object_or_404(ManufacturingOrder, mo_number=mo_number)
except:
messages.error(request, 'Manufacturing Order not found.')
return redirect('manufacturing:mo_list')
if mo.status in ['completed', 'cancelled']:
messages.error(request, f'Cannot cancel Manufacturing Order in {mo.get_status_display()} status.')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
if request.method == 'POST':
mo.status = 'cancelled'
mo.actual_end_date = timezone.now()
mo.save()
messages.success(request, f'Manufacturing Order "{mo.mo_number}" cancelled successfully!')
return redirect('manufacturing:mo_detail', mo_number=mo.mo_number)
context = {
'mo': mo,
'module_title': f'Cancel Manufacturing Order: {mo.mo_number}',
'action': 'Cancel',
}
return render(request, 'manufacturing/mo_confirm_action.html', context)
@login_required
@permission_required('manufacturing.delete_manufacturingorder', raise_exception=True)
def delete_mo_view(request, mo_number):
"""Delete a manufacturing order"""
try:
mo = get_object_or_404(ManufacturingOrder, mo_number=mo_number)
except:
messages.error(request, 'Manufacturing Order not found.')
return redirect('manufacturing:mo_list')
if request.method == 'POST':
mo_number = mo.mo_number
mo.delete()
messages.success(request, f'Manufacturing Order "{mo_number}" deleted successfully!')
return redirect('manufacturing:mo_list')
context = {
'mo': mo,
'module_title': f'Delete Manufacturing Order: {mo.mo_number}',
}
return render(request, 'manufacturing/mo_confirm_delete.html', context)