from django.shortcuts import render, get_object_or_404, redirect from django.urls import reverse from django.contrib.auth.decorators import login_required from django.contrib import messages from django.http import JsonResponse from django.views.decorators.http import require_POST, require_GET from django.db import transaction from django.contrib.auth import get_user_model from .models import Process, ProcessInstance, StepInstance from wells.models import Well from accounts.models import Profile from accounts.forms import CustomerForm from wells.forms import WellForm from wells.models import WaterMeterManufacturer @login_required def request_list(request): """نمایش لیست درخواست‌ها با جدول و مدال ایجاد""" instances = ProcessInstance.objects.select_related('well', 'representative', 'requester').prefetch_related('step_instances__step').filter(is_deleted=False).order_by('-created') processes = Process.objects.filter(is_active=True) manufacturers = WaterMeterManufacturer.objects.all().order_by('name') # Calculate progress for each instance instances_with_progress = [] for instance in instances: total_steps = instance.process.steps.count() completed_steps = instance.step_instances.filter(status='completed').count() progress_percentage = (completed_steps / total_steps * 100) if total_steps > 0 else 0 instances_with_progress.append({ 'instance': instance, 'progress_percentage': round(progress_percentage), 'completed_steps': completed_steps, 'total_steps': total_steps, }) # Summary stats for header cards total_count = instances.count() completed_count = instances.filter(status='completed').count() in_progress_count = instances.filter(status='in_progress').count() pending_count = instances.filter(status='pending').count() return render(request, 'processes/request_list.html', { 'instances_with_progress': instances_with_progress, 'customer_form': CustomerForm(), 'well_form': WellForm(), 'processes': processes, 'manufacturers': manufacturers, 'total_count': total_count, 'completed_count': completed_count, 'in_progress_count': in_progress_count, 'pending_count': pending_count, }) @require_GET @login_required def lookup_well_by_subscription(request): sub = request.GET.get('water_subscription_number', '').strip() if not sub: return JsonResponse({'ok': False, 'error': 'شماره اشتراک الزامی است'}, status=400) try: well = Well.objects.select_related('representative', 'water_meter_manufacturer').get(water_subscription_number=sub) data = { 'id': well.id, 'water_subscription_number': well.water_subscription_number, 'electricity_subscription_number': well.electricity_subscription_number, 'water_meter_serial_number': well.water_meter_serial_number, 'water_meter_old_serial_number': well.water_meter_old_serial_number, 'water_meter_manufacturer': well.water_meter_manufacturer.id if well.water_meter_manufacturer else None, 'utm_x': str(well.utm_x) if well.utm_x is not None else None, 'utm_y': str(well.utm_y) if well.utm_y is not None else None, 'utm_zone': well.utm_zone, 'utm_hemisphere': well.utm_hemisphere, 'well_power': well.well_power, 'reference_letter_number': well.reference_letter_number, 'reference_letter_date': well.reference_letter_date.isoformat() if well.reference_letter_date else None, 'representative_letter_file_url': well.representative_letter_file.url if well.representative_letter_file else '', 'representative_letter_file_name': well.representative_letter_file.name.split('/')[-1] if well.representative_letter_file else '', 'representative_id': well.representative.id if well.representative else None, 'representative_full_name': well.representative.get_full_name() if well.representative else None, } return JsonResponse({'ok': True, 'exists': True, 'well': data}) except Well.DoesNotExist: return JsonResponse({'ok': True, 'exists': False}) @require_GET @login_required def lookup_representative_by_national_code(request): national_code = request.GET.get('national_code', '').strip() if not national_code: return JsonResponse({'ok': False, 'error': 'کد ملی الزامی است'}, status=400) profile = Profile.objects.select_related('user').filter(national_code=national_code).first() if not profile: return JsonResponse({'ok': True, 'exists': False}) user = profile.user return JsonResponse({ 'ok': True, 'exists': True, 'user': { 'id': user.id, 'username': user.username, 'first_name': user.first_name, 'last_name': user.last_name, 'full_name': user.get_full_name(), 'profile': { 'national_code': profile.national_code, 'phone_number_1': profile.phone_number_1, 'phone_number_2': profile.phone_number_2, 'card_number': profile.card_number, 'account_number': profile.account_number, 'bank_name': profile.bank_name, 'address': profile.address, } } }) @require_POST @login_required @transaction.atomic def create_request_with_entities(request): """ایجاد/به‌روزرسانی چاه و نماینده و سپس ایجاد درخواست""" User = get_user_model() process_id = request.POST.get('process') process = Process.objects.get(id=process_id) description = request.POST.get('description', '') # Well fields water_subscription_number = request.POST.get('water_subscription_number') well_id = request.POST.get('well_id') # optional if existing # Representative fields representative_id = request.POST.get('representative_id') if not process_id: return JsonResponse({'ok': False, 'errors': {'request': {'process': ['فرآیند الزامی است']}}}, status=400) if not water_subscription_number: return JsonResponse({'ok': False, 'errors': {'well': {'water_subscription_number': ['شماره اشتراک آب الزامی است']}}}, status=400) if not representative_id and not request.POST.get('national_code'): return JsonResponse({'ok': False, 'errors': {'customer': {'national_code': ['کد ملی نماینده را وارد کنید یا دکمه بررسی/افزودن نماینده را بزنید']}}}, status=400) representative_user = None representative_profile = None if representative_id: representative_profile = Profile.objects.select_related('user').filter(user_id=representative_id).first() if not representative_profile: return JsonResponse({'ok': False, 'errors': {'customer': {'__all__': ['نماینده انتخاب‌شده یافت نشد']}}}, status=400) # Use CustomerForm with request.POST data, merging with existing values customer_form = CustomerForm(request.POST, instance=representative_profile) customer_form.request = request if not customer_form.is_valid(): return JsonResponse({'ok': False, 'errors': {'customer': customer_form.errors}}, status=400) representative_profile = customer_form.save() representative_user = representative_profile.user else: # Use CustomerForm to validate/create/update representative profile by national code profile_instance = None national_code = request.POST.get('national_code') if national_code: profile_instance = Profile.objects.filter(national_code=national_code).first() customer_form = CustomerForm(request.POST, instance=profile_instance) customer_form.request = request if not customer_form.is_valid(): return JsonResponse({'ok': False, 'errors': {'customer': customer_form.errors}}, status=400) representative_profile = customer_form.save() representative_user = representative_profile.user # Resolve/create/update well # Build WellForm data from POST well = None if well_id: well = Well.objects.filter(id=well_id).first() if not well: return JsonResponse({'ok': False, 'error': 'شناسه چاه نامعتبر است'}, status=400) else: existing = Well.objects.filter(water_subscription_number=water_subscription_number).first() if existing: well = existing well_data = request.POST.copy() # Ensure representative set from created/selected user if not provided if representative_user and not well_data.get('representative'): well_data['representative'] = str(representative_user.id) if not well_data.get('water_subscription_number'): well_data['water_subscription_number'] = water_subscription_number # Preserve existing values on partial updates if well: for field_name in WellForm.Meta.fields: if field_name in ('representative_letter_file',): # File field handled via request.FILES; skip if not provided continue incoming = well_data.get(field_name, None) if incoming is None or incoming == '': current_value = getattr(well, field_name, None) if current_value is None: continue # Convert FK to id if hasattr(current_value, 'pk'): well_data[field_name] = str(current_value.pk) else: # Convert dates/decimals/others to string try: well_data[field_name] = current_value.isoformat() # dates except AttributeError: well_data[field_name] = str(current_value) well_form = WellForm(well_data, request.FILES, instance=well) if not well_form.is_valid(): return JsonResponse({'ok': False, 'errors': {'well': well_form.errors}}, status=400) # Save with ability to remove existing file well = well_form.save(commit=False) try: if request.POST.get('remove_file') == 'true' and getattr(well, 'representative_letter_file', None): well.representative_letter_file.delete(save=False) well.representative_letter_file = None except Exception: pass well.save() # Auto fill geo ownership from current user profile if available current_profile = getattr(request.user, 'profile', None) if current_profile: if hasattr(well, 'affairs'): well.affairs = current_profile.affairs if hasattr(well, 'county'): well.county = current_profile.county if hasattr(well, 'broker'): well.broker = current_profile.broker well.save() # Create request instance instance = ProcessInstance.objects.create( process=process, description=description, well=well, representative=representative_user, requester=request.user, broker=request.user.profile.broker if request.user.profile else None, status='pending', priority='medium', ) # ایجاد نمونه‌های مرحله بر اساس مراحل فرآیند و تنظیم مرحله فعلی for step in process.steps.all().order_by('order'): StepInstance.objects.create( process_instance=instance, step=step ) first_step = process.steps.all().order_by('order').first() if first_step: instance.current_step = first_step instance.status = 'in_progress' instance.save() redirect_url = reverse('processes:instance_steps', args=[instance.id]) return JsonResponse({'ok': True, 'instance_id': instance.id, 'redirect': redirect_url}) @require_POST @login_required def delete_request(request, instance_id): """حذف درخواست""" instance = get_object_or_404(ProcessInstance, id=instance_id) code = instance.code if instance.status == 'completed': return JsonResponse({ 'success': False, 'message': 'درخواست تکمیل شده نمی‌تواند حذف شود' }) instance.delete() return JsonResponse({ 'success': True, 'message': f'درخواست {code} با موفقیت حذف شد' }) @login_required def step_detail(request, instance_id, step_id): """نمایش جزئیات مرحله خاص""" instance = get_object_or_404( ProcessInstance.objects.select_related('process', 'well', 'requester', 'representative', 'representative__profile'), id=instance_id ) step = get_object_or_404(instance.process.steps, id=step_id) # If the request is already completed, redirect to read-only summary page if instance.status == 'completed': return redirect('processes:instance_summary', instance_id=instance.id) # جلوگیری از پرش به مراحل آینده: فقط اجازه نمایش مرحله جاری یا مراحل تکمیل‌شده try: if instance.current_step and step.order > instance.current_step.order: messages.error(request, 'ابتدا مراحل قبلی را تکمیل کنید.') return redirect('processes:step_detail', instance_id=instance.id, step_id=instance.current_step.id) except Exception: pass # بررسی دسترسی به مرحله if not instance.can_access_step(step): messages.error(request, 'شما به این مرحله دسترسی ندارید. ابتدا مراحل قبلی را تکمیل کنید.') return redirect('processes:request_list') # هدایت به view مناسب بر اساس نوع مرحله if step.order == 1: # مرحله اول - انتخاب اقلام return redirect('invoices:quote_step', instance_id=instance.id, step_id=step.id) elif step.order == 2: # مرحله دوم - صدور پیش‌فاکتور return redirect('invoices:quote_preview_step', instance_id=instance.id, step_id=step.id) elif step.order == 3: # مرحله سوم - ثبت فیش‌های واریزی return redirect('invoices:quote_payment_step', instance_id=instance.id, step_id=step.id) elif step.order == 4: # مرحله چهارم - قرارداد return redirect('contracts:contract_step', instance_id=instance.id, step_id=step.id) elif step.order == 5: # مرحله پنجم - انتخاب نصاب return redirect('installations:installation_assign_step', instance_id=instance.id, step_id=step.id) elif step.order == 6: # مرحله ششم - گزارش نصب return redirect('installations:installation_report_step', instance_id=instance.id, step_id=step.id) elif step.order == 7: # مرحله هفتم - فاکتور نهایی return redirect('invoices:final_invoice_step', instance_id=instance.id, step_id=step.id) elif step.order == 8: # مرحله هشتم - تسویه حساب نهایی return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id) elif step.order == 9: # مرحله نهم - گواهی نهایی return redirect('certificates:certificate_step', instance_id=instance.id, step_id=step.id) # برای سایر مراحل، template عمومی نمایش داده می‌شود step_instance = instance.step_instances.filter(step=step).first() # Navigation logic previous_step = instance.process.steps.filter(order__lt=step.order).last() next_step = instance.process.steps.filter(order__gt=step.order).first() return render(request, 'processes/step_detail.html', { 'instance': instance, 'step': step, 'step_instance': step_instance, 'previous_step': previous_step, 'next_step': next_step, }) @login_required def instance_steps(request, instance_id): """هدایت به مرحله فعلی instance""" instance = get_object_or_404(ProcessInstance, id=instance_id) if not instance.current_step: # اگر مرحله فعلی تعریف نشده، به اولین مرحله برو first_step = instance.process.steps.first() if first_step: instance.current_step = first_step instance.save() return redirect('processes:step_detail', instance_id=instance.id, step_id=first_step.id) else: messages.error(request, 'هیچ مرحله‌ای برای این فرآیند تعریف نشده است.') return redirect('processes:request_list') # If completed, go to summary instead of steps if instance.status == 'completed': return redirect('processes:instance_summary', instance_id=instance.id) return redirect('processes:step_detail', instance_id=instance.id, step_id=instance.current_step.id) @login_required def instance_summary(request, instance_id): """نمای خلاصهٔ فقط‌خواندنی برای درخواست‌های تکمیل‌شده.""" instance = get_object_or_404(ProcessInstance.objects.select_related('well', 'representative'), id=instance_id) # Only show for completed requests; otherwise route to steps if instance.status != 'completed': return redirect('processes:instance_steps', instance_id=instance.id) # Collect final invoice, payments, and certificate if any from invoices.models import Invoice from installations.models import InstallationReport from certificates.models import CertificateInstance invoice = Invoice.objects.filter(process_instance=instance).first() payments = invoice.payments.filter(is_deleted=False).all() if invoice else [] latest_report = InstallationReport.objects.filter(assignment__process_instance=instance).order_by('-created').first() certificate = CertificateInstance.objects.filter(process_instance=instance).order_by('-created').first() # Build rows like final invoice step rows = [] if invoice: items_qs = invoice.items.select_related('item').filter(is_deleted=False).all() rows = list(items_qs) return render(request, 'processes/instance_summary.html', { 'instance': instance, 'invoice': invoice, 'payments': payments, 'rows': rows, 'latest_report': latest_report, 'certificate': certificate, })