Add qoute step.
This commit is contained in:
		
							parent
							
								
									b71ea45681
								
							
						
					
					
						commit
						6ff4740d04
					
				
					 30 changed files with 3362 additions and 376 deletions
				
			
		| 
						 | 
				
			
			@ -1,10 +1,21 @@
 | 
			
		|||
from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
import json
 | 
			
		||||
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
 | 
			
		||||
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 .forms import ProcessInstanceForm
 | 
			
		||||
from accounts.forms import CustomerForm
 | 
			
		||||
from wells.forms import WellForm
 | 
			
		||||
from wells.models import WaterMeterManufacturer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def process_list(request):
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +33,272 @@ def process_detail(request, process_id):
 | 
			
		|||
        'process': process
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def request_list(request):
 | 
			
		||||
    """نمایش لیست درخواستها با جدول و مدال ایجاد"""
 | 
			
		||||
    instances = ProcessInstance.objects.select_related('well', 'representative', 'requester').filter(is_deleted=False).order_by('-created')
 | 
			
		||||
    processes = Process.objects.filter(is_active=True)
 | 
			
		||||
    manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
 | 
			
		||||
    return render(request, 'processes/request_list.html', {
 | 
			
		||||
        'instances': instances,
 | 
			
		||||
        'customer_form': CustomerForm(),
 | 
			
		||||
        'well_form': WellForm(),
 | 
			
		||||
        'processes': processes,
 | 
			
		||||
        'manufacturers': manufacturers
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@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,
 | 
			
		||||
                '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')
 | 
			
		||||
    # Prefer plain CustomerForm keys; fallback to representative_* keys
 | 
			
		||||
    representative_national_code = request.POST.get('national_code') or request.POST.get('representative_national_code')
 | 
			
		||||
    representative_first_name = request.POST.get('first_name') or request.POST.get('representative_first_name')
 | 
			
		||||
    representative_last_name = request.POST.get('last_name') or request.POST.get('representative_last_name')
 | 
			
		||||
    representative_username = request.POST.get('username') or request.POST.get('representative_username')
 | 
			
		||||
    representative_phone_number_1 = request.POST.get('phone_number_1') or request.POST.get('representative_phone_number_1')
 | 
			
		||||
    representative_phone_number_2 = request.POST.get('phone_number_2') or request.POST.get('representative_phone_number_2')
 | 
			
		||||
    representative_card_number = request.POST.get('card_number') or request.POST.get('representative_card_number')
 | 
			
		||||
    representative_account_number = request.POST.get('account_number') or request.POST.get('representative_account_number')
 | 
			
		||||
    representative_address = request.POST.get('address') or request.POST.get('representative_address')
 | 
			
		||||
 | 
			
		||||
    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 representative_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)
 | 
			
		||||
        representative_user = representative_profile.user
 | 
			
		||||
        # Optionally update if fields provided
 | 
			
		||||
        changed = False
 | 
			
		||||
        if representative_first_name:
 | 
			
		||||
            representative_user.first_name = representative_first_name
 | 
			
		||||
            changed = True
 | 
			
		||||
        if representative_last_name:
 | 
			
		||||
            representative_user.last_name = representative_last_name
 | 
			
		||||
            changed = True
 | 
			
		||||
        if representative_username:
 | 
			
		||||
            representative_user.username = representative_username
 | 
			
		||||
            changed = True
 | 
			
		||||
        if changed:
 | 
			
		||||
            representative_user.save()
 | 
			
		||||
        if representative_national_code:
 | 
			
		||||
            representative_profile.national_code = representative_national_code
 | 
			
		||||
        if representative_phone_number_1 is not None:
 | 
			
		||||
            representative_profile.phone_number_1 = representative_phone_number_1
 | 
			
		||||
        if representative_phone_number_2 is not None:
 | 
			
		||||
            representative_profile.phone_number_2 = representative_phone_number_2
 | 
			
		||||
        if representative_card_number is not None:
 | 
			
		||||
            representative_profile.card_number = representative_card_number
 | 
			
		||||
        if representative_account_number is not None:
 | 
			
		||||
            representative_profile.account_number = representative_account_number
 | 
			
		||||
        if representative_address is not None:
 | 
			
		||||
            representative_profile.address = representative_address
 | 
			
		||||
        representative_profile.save()
 | 
			
		||||
    else:
 | 
			
		||||
        # Use CustomerForm to validate/create/update representative profile by national code
 | 
			
		||||
        profile_instance = None
 | 
			
		||||
        if representative_national_code:
 | 
			
		||||
            profile_instance = Profile.objects.filter(national_code=representative_national_code).first()
 | 
			
		||||
        customer_data = {
 | 
			
		||||
            'first_name': representative_first_name or '',
 | 
			
		||||
            'last_name': representative_last_name or '',
 | 
			
		||||
            'phone_number_1': representative_phone_number_1 or '',
 | 
			
		||||
            'phone_number_2': representative_phone_number_2 or '',
 | 
			
		||||
            'national_code': representative_national_code or '',
 | 
			
		||||
            'address': representative_address or '',
 | 
			
		||||
            'card_number': representative_card_number or '',
 | 
			
		||||
            'account_number': representative_account_number or '',
 | 
			
		||||
        }
 | 
			
		||||
        customer_form = CustomerForm(customer_data, 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,
 | 
			
		||||
        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
 | 
			
		||||
    instance.delete()
 | 
			
		||||
    return JsonResponse({
 | 
			
		||||
        'success': True,
 | 
			
		||||
        'message': f'درخواست {code} با موفقیت حذف شد'
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def start_process(request, process_id):
 | 
			
		||||
    """شروع فرآیند جدید"""
 | 
			
		||||
| 
						 | 
				
			
			@ -67,6 +344,61 @@ def instance_detail(request, instance_id):
 | 
			
		|||
        'instance': instance
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@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 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)
 | 
			
		||||
    
 | 
			
		||||
    # برای سایر مراحل، 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')
 | 
			
		||||
    
 | 
			
		||||
    return redirect('processes:step_detail', instance_id=instance.id, step_id=instance.current_step.id)
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def my_processes(request):
 | 
			
		||||
    """نمایش فرآیندهای کاربر"""
 | 
			
		||||
| 
						 | 
				
			
			@ -76,4 +408,5 @@ def my_processes(request):
 | 
			
		|||
    return render(request, 'processes/my_processes.html', {
 | 
			
		||||
        'my_instances': my_instances,
 | 
			
		||||
        'assigned_steps': assigned_steps
 | 
			
		||||
    })
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue