429 lines
20 KiB
Python
429 lines
20 KiB
Python
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, 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):
|
|
"""نمایش لیست فرآیندهای فعال"""
|
|
processes = Process.objects.filter(is_active=True)
|
|
return render(request, 'processes/process_list.html', {
|
|
'processes': processes
|
|
})
|
|
|
|
@login_required
|
|
def process_detail(request, process_id):
|
|
"""نمایش جزئیات فرآیند"""
|
|
process = get_object_or_404(Process, id=process_id, is_active=True)
|
|
return render(request, 'processes/process_detail.html', {
|
|
'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,
|
|
'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')
|
|
# 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_bank_name = request.POST.get('bank_name') or request.POST.get('representative_bank_name')
|
|
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_bank_name is not None:
|
|
representative_profile.bank_name = representative_bank_name
|
|
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 '',
|
|
'bank_name': representative_bank_name 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):
|
|
"""شروع فرآیند جدید"""
|
|
process = get_object_or_404(Process, id=process_id, is_active=True)
|
|
|
|
if request.method == 'POST':
|
|
form = ProcessInstanceForm(request.POST)
|
|
if form.is_valid():
|
|
instance = form.save(commit=False)
|
|
instance.process = process
|
|
instance.requester = request.user
|
|
instance.save()
|
|
|
|
# ایجاد نمونههای مرحله
|
|
for step in process.steps.all():
|
|
StepInstance.objects.create(
|
|
process_instance=instance,
|
|
step=step
|
|
)
|
|
|
|
# تنظیم مرحله اول به عنوان مرحله فعلی
|
|
first_step = process.steps.first()
|
|
if first_step:
|
|
instance.current_step = first_step
|
|
instance.status = 'in_progress'
|
|
instance.save()
|
|
|
|
messages.success(request, f'فرآیند {process.name} با موفقیت شروع شد.')
|
|
return redirect('processes:instance_detail', instance_id=instance.id)
|
|
else:
|
|
form = ProcessInstanceForm()
|
|
|
|
return render(request, 'processes/start_process.html', {
|
|
'process': process,
|
|
'form': form
|
|
})
|
|
|
|
@login_required
|
|
def instance_detail(request, instance_id):
|
|
"""نمایش جزئیات نمونه فرآیند"""
|
|
instance = get_object_or_404(ProcessInstance, id=instance_id)
|
|
return render(request, 'processes/instance_detail.html', {
|
|
'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)
|
|
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')
|
|
|
|
return redirect('processes:step_detail', instance_id=instance.id, step_id=instance.current_step.id)
|
|
|
|
@login_required
|
|
def my_processes(request):
|
|
"""نمایش فرآیندهای کاربر"""
|
|
my_instances = ProcessInstance.objects.filter(requester=request.user)
|
|
assigned_steps = StepInstance.objects.filter(assigned_to=request.user, status='in_progress')
|
|
|
|
return render(request, 'processes/my_processes.html', {
|
|
'my_instances': my_instances,
|
|
'assigned_steps': assigned_steps
|
|
})
|
|
|