from django.shortcuts import render, redirect, get_object_or_404 from django.views.generic import ListView, DetailView from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.contrib import messages from django.http import JsonResponse from .models import ManufacturingOrder, BillOfMaterials from inventory.models import Product from .forms import ManufacturingOrderForm, BillOfMaterialsForm, MultipleBillOfMaterialsForm, BOMComponentFormSet from users.views import has_manufacturing_access @method_decorator(login_required, name='dispatch') class ManufactureListView(ListView): model = ManufacturingOrder template_name = 'manufacture/manufacture_list.html' context_object_name = 'manufacturing_orders' paginate_by = 20 @method_decorator(login_required, name='dispatch') class BillOfMaterialsListView(ListView): model = BillOfMaterials template_name = 'manufacture/bom_list.html' context_object_name = 'boms' paginate_by = 20 def get_queryset(self): queryset = super().get_queryset() # Optionally filter by manufactured product product_id = self.request.GET.get('product_id') if product_id: queryset = queryset.filter(manufactured_product_id=product_id) return queryset @login_required def manufacture_create(request): """Create a new manufacturing order with simple input""" if request.method == 'POST': form = ManufacturingOrderForm(request.POST) if form.is_valid(): manufacturing_order = form.save(commit=False) manufacturing_order.created_by = request.user # Generate order number if not provided if not manufacturing_order.order_number: # Get the next order number last_order = ManufacturingOrder.objects.order_by('-id').first() if last_order: try: last_number = int(last_order.order_number.split('-')[-1]) new_number = last_number + 1 except: new_number = 1 else: new_number = 1 manufacturing_order.order_number = f"MO-{new_number:06d}" # Set status to completed by default for simplicity manufacturing_order.status = 'completed' manufacturing_order.save() messages.success(request, f'Manufacturing order "{manufacturing_order.order_number}" created successfully!') return redirect('manufacture:manufacture_detail', pk=manufacturing_order.pk) else: form = ManufacturingOrderForm() return render(request, 'manufacture/manufacture_form.html', {'form': form, 'title': 'Create Manufacturing Order'}) @login_required def manufacture_edit(request, pk): """Edit an existing manufacturing order""" if not has_manufacturing_access(request.user): messages.error(request, 'You do not have permission to edit manufacturing orders.') return redirect('manufacture:manufacture_detail', pk=pk) manufacturing_order = get_object_or_404(ManufacturingOrder, pk=pk) if request.method == 'POST': form = ManufacturingOrderForm(request.POST, instance=manufacturing_order) if form.is_valid(): form.save() messages.success(request, f'Manufacturing order "{manufacturing_order.order_number}" updated successfully!') return redirect('manufacture:manufacture_detail', pk=manufacturing_order.pk) else: form = ManufacturingOrderForm(instance=manufacturing_order) return render(request, 'manufacture/manufacture_form.html', { 'form': form, 'title': 'Edit Manufacturing Order', 'manufacturing_order': manufacturing_order }) @login_required def manufacture_delete(request, pk): """Delete a manufacturing order""" if not has_manufacturing_access(request.user): messages.error(request, 'You do not have permission to delete manufacturing orders.') return redirect('manufacture:manufacture_list') manufacturing_order = get_object_or_404(ManufacturingOrder, pk=pk) if request.method == 'POST': order_number = manufacturing_order.order_number manufacturing_order.delete() messages.success(request, f'Manufacturing order "{order_number}" deleted successfully!') return redirect('manufacture:manufacture_list') return render(request, 'manufacture/manufacture_confirm_delete.html', {'manufacturing_order': manufacturing_order}) @login_required def bom_create(request): """Create a new bill of materials entry""" if not has_manufacturing_access(request.user): messages.error(request, 'You do not have permission to create bill of materials entries.') return redirect('manufacture:bom_list') if request.method == 'POST': main_form = MultipleBillOfMaterialsForm(request.POST) component_formset = BOMComponentFormSet(request.POST) if main_form.is_valid() and component_formset.is_valid(): manufactured_product = main_form.cleaned_data['manufactured_product'] # Save all component entries saved_components = [] for component_form in component_formset: # Skip empty forms if not component_form.cleaned_data: continue component = component_form.cleaned_data['component'] quantity = component_form.cleaned_data['quantity'] unit = component_form.cleaned_data['unit'] # Create BOM entry bom = BillOfMaterials( manufactured_product=manufactured_product, component=component, quantity=quantity, unit=unit ) bom.save() saved_components.append(bom) messages.success(request, f'Bill of Materials for "{manufactured_product.name}" with {len(saved_components)} components created successfully!') return redirect('manufacture:bom_list') else: main_form = MultipleBillOfMaterialsForm() component_formset = BOMComponentFormSet() return render(request, 'manufacture/bom_form.html', { 'main_form': main_form, 'component_formset': component_formset, 'title': 'Create Bill of Materials' }) @login_required def bom_edit(request, pk): """Edit an existing bill of materials entry""" if not has_manufacturing_access(request.user): messages.error(request, 'You do not have permission to edit bill of materials.') return redirect('manufacture:bom_list') # Get the BOM entry to edit bom = get_object_or_404(BillOfMaterials, pk=pk) if request.method == 'POST': main_form = MultipleBillOfMaterialsForm(request.POST) component_formset = BOMComponentFormSet(request.POST) if main_form.is_valid() and component_formset.is_valid(): manufactured_product = main_form.cleaned_data['manufactured_product'] # Delete existing BOM entries for this manufactured product BillOfMaterials.objects.filter(manufactured_product=manufactured_product).delete() # Save all component entries saved_components = [] for component_form in component_formset: # Skip empty forms if not component_form.cleaned_data: continue component = component_form.cleaned_data['component'] quantity = component_form.cleaned_data['quantity'] unit = component_form.cleaned_data['unit'] # Create BOM entry bom_entry = BillOfMaterials( manufactured_product=manufactured_product, component=component, quantity=quantity, unit=unit ) bom_entry.save() saved_components.append(bom_entry) messages.success(request, f'Bill of Materials for "{manufactured_product.name}" with {len(saved_components)} components updated successfully!') return redirect('manufacture:bom_list') else: # Pre-populate the form with existing components main_form = MultipleBillOfMaterialsForm(initial={'manufactured_product': bom.manufactured_product}) # Get all existing components for this manufactured product existing_components = BillOfMaterials.objects.filter(manufactured_product=bom.manufactured_product) # Create initial data for the formset initial_data = [] for existing_bom in existing_components: initial_data.append({ 'component': existing_bom.component, 'quantity': existing_bom.quantity, 'unit': existing_bom.unit }) # Add extra empty forms if needed while len(initial_data) < 1: initial_data.append({}) component_formset = BOMComponentFormSet(initial=initial_data) return render(request, 'manufacture/bom_form.html', { 'main_form': main_form, 'component_formset': component_formset, 'title': 'Edit Bill of Materials', 'bom': bom }) @login_required def bom_delete(request, pk): """Delete a bill of materials entry""" if not has_manufacturing_access(request.user): messages.error(request, 'You do not have permission to delete bill of materials entries.') return redirect('manufacture:bom_list') bom = get_object_or_404(BillOfMaterials, pk=pk) if request.method == 'POST': manufactured_product_name = bom.manufactured_product.name bom.delete() messages.success(request, f'Bill of Materials entry for "{manufactured_product_name}" deleted successfully!') return redirect('manufacture:bom_list') return render(request, 'manufacture/bom_confirm_delete.html', {'bom': bom}) @method_decorator(login_required, name='dispatch') class ManufactureDetailView(DetailView): model = ManufacturingOrder template_name = 'manufacture/manufacture_detail.html' context_object_name = 'manufacturing_order' @method_decorator(login_required, name='dispatch') class BillOfMaterialsDetailView(DetailView): model = BillOfMaterials template_name = 'manufacture/bom_detail.html' context_object_name = 'bom' @login_required def get_product_info(request, product_id): """Return product information including unit as JSON""" if not has_manufacturing_access(request.user): return JsonResponse({'error': 'Permission denied'}, status=403) try: product = Product.objects.get(id=product_id) return JsonResponse({ 'id': product.id, 'name': product.name, 'unit': product.unit, 'unit_display': product.get_unit_display() }) except Product.DoesNotExist: return JsonResponse({'error': 'Product not found'}, status=404)