huge fix
This commit is contained in:
parent
810c87e2e0
commit
b5bf3a5dbe
51 changed files with 2397 additions and 326 deletions
|
|
@ -3,13 +3,19 @@ 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.http import JsonResponse, HttpResponse
|
||||
from django.views.decorators.http import require_POST, require_GET
|
||||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
from django.contrib.auth import get_user_model
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, Alignment, PatternFill
|
||||
from openpyxl.utils import get_column_letter
|
||||
from datetime import datetime
|
||||
from _helpers.utils import persian_converter3
|
||||
from .models import Process, ProcessInstance, StepInstance, ProcessStep
|
||||
from .utils import scope_instances_queryset, get_scoped_instance_or_404
|
||||
from installations.models import InstallationAssignment
|
||||
from installations.models import InstallationAssignment, InstallationReport
|
||||
from wells.models import Well
|
||||
from accounts.models import Profile, Broker
|
||||
from locations.models import Affairs
|
||||
|
|
@ -65,18 +71,65 @@ def request_list(request):
|
|||
steps_list = ProcessStep.objects.select_related('process').all().order_by('process__name', 'order')
|
||||
manufacturers = WaterMeterManufacturer.objects.all().order_by('name')
|
||||
|
||||
# Calculate progress for each instance
|
||||
# Prepare installation assignments map (scheduled date by instance)
|
||||
try:
|
||||
instance_ids = list(instances.values_list('id', flat=True))
|
||||
except Exception:
|
||||
instance_ids = []
|
||||
assignments_map = {}
|
||||
reports_map = {}
|
||||
if instance_ids:
|
||||
try:
|
||||
ass_qs = InstallationAssignment.objects.filter(process_instance_id__in=instance_ids).values('process_instance_id', 'scheduled_date')
|
||||
for row in ass_qs:
|
||||
assignments_map[row['process_instance_id']] = row['scheduled_date']
|
||||
except Exception:
|
||||
assignments_map = {}
|
||||
# latest report per instance (visited_date)
|
||||
try:
|
||||
rep_qs = InstallationReport.objects.filter(assignment__process_instance_id__in=instance_ids).order_by('-created').values('assignment__process_instance_id', 'visited_date')
|
||||
for row in rep_qs:
|
||||
pid = row['assignment__process_instance_id']
|
||||
if pid not in reports_map:
|
||||
reports_map[pid] = row['visited_date']
|
||||
except Exception:
|
||||
reports_map = {}
|
||||
|
||||
# Calculate progress for each instance and attach install schedule info
|
||||
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
|
||||
|
||||
sched_date = assignments_map.get(instance.id)
|
||||
overdue_days = 0
|
||||
reference_date = None
|
||||
if sched_date:
|
||||
# Reference date: until installer submits a report, use today; otherwise use visited_date
|
||||
try:
|
||||
visited_date = reports_map.get(instance.id)
|
||||
if visited_date:
|
||||
reference_date = visited_date
|
||||
else:
|
||||
try:
|
||||
reference_date = timezone.localdate()
|
||||
except Exception:
|
||||
from datetime import date as _date
|
||||
reference_date = _date.today()
|
||||
if reference_date > sched_date:
|
||||
overdue_days = (reference_date - sched_date).days
|
||||
except Exception:
|
||||
overdue_days = 0
|
||||
reference_date = None
|
||||
|
||||
installation_scheduled_date = reference_date if reference_date and reference_date > sched_date else sched_date
|
||||
instances_with_progress.append({
|
||||
'instance': instance,
|
||||
'progress_percentage': round(progress_percentage),
|
||||
'completed_steps': completed_steps,
|
||||
'total_steps': total_steps,
|
||||
'installation_scheduled_date': installation_scheduled_date,
|
||||
'installation_overdue_days': overdue_days,
|
||||
})
|
||||
|
||||
# Summary stats for header cards
|
||||
|
|
@ -160,7 +213,10 @@ def lookup_representative_by_national_code(request):
|
|||
'last_name': user.last_name,
|
||||
'full_name': user.get_full_name(),
|
||||
'profile': {
|
||||
'user_type': profile.user_type,
|
||||
'national_code': profile.national_code,
|
||||
'company_name': profile.company_name,
|
||||
'company_national_id': profile.company_national_id,
|
||||
'phone_number_1': profile.phone_number_1,
|
||||
'phone_number_2': profile.phone_number_2,
|
||||
'card_number': profile.card_number,
|
||||
|
|
@ -240,6 +296,7 @@ def create_request_with_entities(request):
|
|||
well = existing
|
||||
|
||||
well_data = request.POST.copy()
|
||||
print(well_data)
|
||||
# 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)
|
||||
|
|
@ -366,12 +423,12 @@ def step_detail(request, instance_id, step_id):
|
|||
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
|
||||
# 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):
|
||||
|
|
@ -471,4 +528,365 @@ def instance_summary(request, instance_id):
|
|||
'latest_report': latest_report,
|
||||
'certificate': certificate,
|
||||
})
|
||||
|
||||
|
||||
def format_date_jalali(date_obj):
|
||||
"""Convert date to Jalali format without time"""
|
||||
if not date_obj:
|
||||
return ""
|
||||
try:
|
||||
# If it's a datetime, get just the date part
|
||||
if hasattr(date_obj, 'date'):
|
||||
date_obj = date_obj.date()
|
||||
return persian_converter3(date_obj)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def format_datetime_jalali(datetime_obj):
|
||||
"""Convert datetime to Jalali format without time"""
|
||||
if not datetime_obj:
|
||||
return ""
|
||||
try:
|
||||
# Get just the date part
|
||||
date_part = datetime_obj.date() if hasattr(datetime_obj, 'date') else datetime_obj
|
||||
return persian_converter3(date_part)
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
@login_required
|
||||
def export_requests_excel(request):
|
||||
"""Export filtered requests to Excel"""
|
||||
|
||||
# Get the same queryset as request_list view (with filters)
|
||||
instances = ProcessInstance.objects.select_related(
|
||||
'process', 'current_step', 'representative', 'well', 'well__county', 'well__affairs'
|
||||
).prefetch_related('step_instances')
|
||||
|
||||
# Apply scoping
|
||||
instances = scope_instances_queryset(request.user, instances)
|
||||
|
||||
# Apply filters (same logic as request_list view)
|
||||
filter_status = request.GET.get('status', '').strip()
|
||||
if filter_status:
|
||||
instances = instances.filter(status=filter_status)
|
||||
|
||||
filter_affairs = request.GET.get('affairs', '').strip()
|
||||
if filter_affairs and filter_affairs.isdigit():
|
||||
instances = instances.filter(well__affairs_id=filter_affairs)
|
||||
|
||||
filter_broker = request.GET.get('broker', '').strip()
|
||||
if filter_broker and filter_broker.isdigit():
|
||||
instances = instances.filter(well__broker_id=filter_broker)
|
||||
|
||||
filter_step = request.GET.get('step', '').strip()
|
||||
if filter_step and filter_step.isdigit():
|
||||
instances = instances.filter(current_step_id=filter_step)
|
||||
|
||||
# Get installation data
|
||||
assignment_ids = list(instances.values_list('id', flat=True))
|
||||
assignments_map = {}
|
||||
reports_map = {}
|
||||
installers_map = {}
|
||||
|
||||
if assignment_ids:
|
||||
assignments = InstallationAssignment.objects.filter(
|
||||
process_instance_id__in=assignment_ids
|
||||
).select_related('process_instance', 'installer')
|
||||
assignments_map = {a.process_instance_id: a.scheduled_date for a in assignments}
|
||||
installers_map = {a.process_instance_id: a.installer for a in assignments}
|
||||
|
||||
reports = InstallationReport.objects.filter(
|
||||
assignment__process_instance_id__in=assignment_ids
|
||||
).select_related('assignment')
|
||||
reports_map = {r.assignment.process_instance_id: r for r in reports}
|
||||
|
||||
# Get quotes and payments data
|
||||
from invoices.models import Quote, Payment, Invoice
|
||||
quotes_map = {}
|
||||
payments_map = {}
|
||||
settlement_dates_map = {}
|
||||
approval_dates_map = {}
|
||||
approval_users_map = {}
|
||||
|
||||
if assignment_ids:
|
||||
# Get quotes
|
||||
quotes = Quote.objects.filter(
|
||||
process_instance_id__in=assignment_ids
|
||||
).select_related('process_instance')
|
||||
quotes_map = {q.process_instance_id: q for q in quotes}
|
||||
|
||||
# Get payments with reference numbers
|
||||
payments = Payment.objects.filter(
|
||||
invoice__process_instance_id__in=assignment_ids,
|
||||
is_deleted=False
|
||||
).select_related('invoice__process_instance').order_by('created')
|
||||
|
||||
for payment in payments:
|
||||
if payment.invoice.process_instance_id not in payments_map:
|
||||
payments_map[payment.invoice.process_instance_id] = []
|
||||
payments_map[payment.invoice.process_instance_id].append(payment)
|
||||
|
||||
# Get final invoices to check settlement dates
|
||||
invoices = Invoice.objects.filter(
|
||||
process_instance_id__in=assignment_ids
|
||||
).select_related('process_instance')
|
||||
|
||||
for invoice in invoices:
|
||||
if invoice.remaining_amount == 0: # Fully settled
|
||||
# Find the last payment date for this invoice
|
||||
last_payment = Payment.objects.filter(
|
||||
invoice__process_instance=invoice.process_instance,
|
||||
is_deleted=False
|
||||
).order_by('-created').first()
|
||||
if last_payment:
|
||||
settlement_dates_map[invoice.process_instance_id] = last_payment.created
|
||||
|
||||
# Get installation approval data
|
||||
from processes.models import StepInstance, StepApproval
|
||||
installation_steps = StepInstance.objects.filter(
|
||||
process_instance_id__in=assignment_ids,
|
||||
step__slug='installation_report', # Assuming this is the slug for installation step
|
||||
status='completed'
|
||||
).select_related('process_instance')
|
||||
|
||||
for step_instance in installation_steps:
|
||||
# Get the approval that completed this step
|
||||
approval = StepApproval.objects.filter(
|
||||
step_instance=step_instance,
|
||||
decision='approved',
|
||||
is_deleted=False
|
||||
).select_related('approved_by').order_by('-created').first()
|
||||
|
||||
if approval:
|
||||
approval_dates_map[step_instance.process_instance_id] = approval.created
|
||||
approval_users_map[step_instance.process_instance_id] = approval.approved_by
|
||||
|
||||
# Calculate progress and installation data
|
||||
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
|
||||
|
||||
sched_date = assignments_map.get(instance.id)
|
||||
overdue_days = 0
|
||||
reference_date = None
|
||||
|
||||
if sched_date:
|
||||
try:
|
||||
report = reports_map.get(instance.id)
|
||||
if report and report.visited_date:
|
||||
reference_date = report.visited_date
|
||||
else:
|
||||
try:
|
||||
reference_date = timezone.localdate()
|
||||
except Exception:
|
||||
from datetime import date as _date
|
||||
reference_date = _date.today()
|
||||
if reference_date > sched_date:
|
||||
overdue_days = (reference_date - sched_date).days
|
||||
except Exception:
|
||||
overdue_days = 0
|
||||
|
||||
installation_scheduled_date = reference_date if reference_date and reference_date > sched_date else sched_date
|
||||
|
||||
instances_with_progress.append({
|
||||
'instance': instance,
|
||||
'progress_percentage': round(progress_percentage),
|
||||
'completed_steps': completed_steps,
|
||||
'total_steps': total_steps,
|
||||
'installation_scheduled_date': installation_scheduled_date,
|
||||
'installation_overdue_days': overdue_days,
|
||||
})
|
||||
|
||||
# Create Excel workbook
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "لیست درخواستها"
|
||||
|
||||
# Set RTL (Right-to-Left) direction
|
||||
ws.sheet_view.rightToLeft = True
|
||||
|
||||
# Define column headers
|
||||
headers = [
|
||||
'شناسه',
|
||||
'تاریخ ایجاد درخواست',
|
||||
'نام نماینده',
|
||||
'نام خانوادگی نماینده',
|
||||
'کد ملی نماینده',
|
||||
'نام شرکت',
|
||||
'شناسه شرکت',
|
||||
'سریال کنتور',
|
||||
'سریال کنتور جدید',
|
||||
'شماره اشتراک آب',
|
||||
'شماره اشتراک برق',
|
||||
'قدرت چاه',
|
||||
'شماره تماس ۱',
|
||||
'شماره تماس ۲',
|
||||
'آدرس',
|
||||
'مبلغ پیشفاکتور',
|
||||
'تاریخ واریزیها و کدهای رهگیری',
|
||||
'تاریخ مراجعه نصاب',
|
||||
'تاخیر نصاب',
|
||||
'نام نصاب',
|
||||
'تاریخ تایید نصب توسط مدیر',
|
||||
'نام تایید کننده نصب',
|
||||
'تاریخ تسویه'
|
||||
]
|
||||
|
||||
# Write headers
|
||||
for col, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col, value=header)
|
||||
cell.font = Font(bold=True)
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.fill = PatternFill(start_color="CCCCCC", end_color="CCCCCC", fill_type="solid")
|
||||
|
||||
# Write data rows
|
||||
for row_num, item in enumerate(instances_with_progress, 2):
|
||||
instance = item['instance']
|
||||
|
||||
# Get representative info
|
||||
rep_first_name = ""
|
||||
rep_last_name = ""
|
||||
rep_national_code = ""
|
||||
rep_phone_1 = ""
|
||||
rep_phone_2 = ""
|
||||
rep_address = ""
|
||||
company_name = ""
|
||||
company_national_id = ""
|
||||
|
||||
if instance.representative:
|
||||
rep_first_name = instance.representative.first_name or ""
|
||||
rep_last_name = instance.representative.last_name or ""
|
||||
if hasattr(instance.representative, 'profile') and instance.representative.profile:
|
||||
profile = instance.representative.profile
|
||||
rep_national_code = profile.national_code or ""
|
||||
rep_phone_1 = profile.phone_number_1 or ""
|
||||
rep_phone_2 = profile.phone_number_2 or ""
|
||||
rep_address = profile.address or ""
|
||||
if profile.user_type == 'legal':
|
||||
company_name = profile.company_name or ""
|
||||
company_national_id = profile.company_national_id or ""
|
||||
|
||||
# Get well info
|
||||
water_subscription = ""
|
||||
electricity_subscription = ""
|
||||
well_power = ""
|
||||
old_meter_serial = ""
|
||||
if instance.well:
|
||||
water_subscription = instance.well.water_subscription_number or ""
|
||||
electricity_subscription = instance.well.electricity_subscription_number or ""
|
||||
well_power = str(instance.well.well_power) if instance.well.well_power else ""
|
||||
old_meter_serial = instance.well.water_meter_serial_number or ""
|
||||
|
||||
# Get new meter serial from installation report
|
||||
new_meter_serial = ""
|
||||
installer_visit_date = ""
|
||||
report = reports_map.get(instance.id)
|
||||
if report:
|
||||
new_meter_serial = report.new_water_meter_serial or ""
|
||||
installer_visit_date = format_date_jalali(report.visited_date)
|
||||
|
||||
# Get quote amount
|
||||
quote_amount = ""
|
||||
quote = quotes_map.get(instance.id)
|
||||
if quote:
|
||||
quote_amount = str(quote.final_amount) if quote.final_amount else ""
|
||||
|
||||
# Get payments info
|
||||
payments_info = ""
|
||||
payments = payments_map.get(instance.id, [])
|
||||
if payments:
|
||||
payment_strings = []
|
||||
for payment in payments:
|
||||
date_str = format_datetime_jalali(payment.created)
|
||||
reference_number = payment.reference_number or "بدون کد"
|
||||
payment_strings.append(f"{date_str} - {reference_number}")
|
||||
payments_info = " | ".join(payment_strings)
|
||||
|
||||
# Get installer name
|
||||
installer_name = ""
|
||||
installer = installers_map.get(instance.id)
|
||||
if installer:
|
||||
installer_name = installer.get_full_name() or str(installer)
|
||||
|
||||
# Get overdue days
|
||||
overdue_days = ""
|
||||
if item['installation_overdue_days'] and item['installation_overdue_days'] > 0:
|
||||
overdue_days = str(item['installation_overdue_days'])
|
||||
|
||||
# Get approval info
|
||||
approval_date = ""
|
||||
approval_user = ""
|
||||
approval_date_obj = approval_dates_map.get(instance.id)
|
||||
approval_user_obj = approval_users_map.get(instance.id)
|
||||
if approval_date_obj:
|
||||
approval_date = format_datetime_jalali(approval_date_obj)
|
||||
if approval_user_obj:
|
||||
approval_user = approval_user_obj.get_full_name() or str(approval_user_obj)
|
||||
|
||||
# Get settlement date
|
||||
settlement_date = ""
|
||||
settlement_date_obj = settlement_dates_map.get(instance.id)
|
||||
if settlement_date_obj:
|
||||
settlement_date = format_datetime_jalali(settlement_date_obj)
|
||||
|
||||
row_data = [
|
||||
instance.code, # شناسه
|
||||
format_datetime_jalali(instance.created), # تاریخ ایجاد درخواست
|
||||
rep_first_name, # نام نماینده
|
||||
rep_last_name, # نام خانوادگی نماینده
|
||||
rep_national_code, # کد ملی نماینده
|
||||
company_name, # نام شرکت
|
||||
company_national_id, # شناسه شرکت
|
||||
old_meter_serial, # سریال کنتور
|
||||
new_meter_serial, # سریال کنتور جدید
|
||||
water_subscription, # شماره اشتراک آب
|
||||
electricity_subscription, # شماره اشتراک برق
|
||||
well_power, # قدرت چاه
|
||||
rep_phone_1, # شماره تماس ۱
|
||||
rep_phone_2, # شماره تماس ۲
|
||||
rep_address, # آدرس
|
||||
quote_amount, # مبلغ پیشفاکتور
|
||||
payments_info, # تاریخ واریزیها و کدهای رهگیری
|
||||
installer_visit_date, # تاریخ مراجعه نصاب
|
||||
overdue_days, # تاخیر نصاب
|
||||
installer_name, # نام نصاب
|
||||
approval_date, # تاریخ تایید نصب توسط مدیر
|
||||
approval_user, # نام تایید کننده نصب
|
||||
settlement_date # تاریخ تسویه
|
||||
]
|
||||
|
||||
for col, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=row_num, column=col, value=value)
|
||||
# Set right alignment for Persian text
|
||||
cell.alignment = Alignment(horizontal='right')
|
||||
|
||||
# Auto-adjust column widths
|
||||
for col in range(1, len(headers) + 1):
|
||||
column_letter = get_column_letter(col)
|
||||
max_length = 0
|
||||
for row in ws[column_letter]:
|
||||
try:
|
||||
if len(str(row.value)) > max_length:
|
||||
max_length = len(str(row.value))
|
||||
except:
|
||||
pass
|
||||
adjusted_width = min(max_length + 2, 50)
|
||||
ws.column_dimensions[column_letter].width = adjusted_width
|
||||
|
||||
# Prepare response
|
||||
response = HttpResponse(
|
||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
|
||||
# Generate filename with current date
|
||||
current_date = datetime.now().strftime('%Y%m%d_%H%M')
|
||||
filename = f'requests_export_{current_date}.xlsx'
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
|
||||
# Save workbook to response
|
||||
wb.save(response)
|
||||
|
||||
return response
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue