shafafiyat/certificates/views.py

227 lines
12 KiB
Python

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import JsonResponse
from django.urls import reverse
from django.utils import timezone
from django.template import Template, Context
from django.utils.safestring import mark_safe
from django.db import IntegrityError
from processes.models import ProcessInstance, StepInstance
from invoices.models import Invoice
from installations.models import InstallationReport
from .models import CertificateTemplate, CertificateInstance
from common.consts import UserRoles
from common.decorators import allowed_roles
from _helpers.jalali import Gregorian
from processes.utils import get_scoped_instance_or_404
def _to_jalali(date_obj):
try:
g = Gregorian(date_obj)
y, m, d = g.persian_tuple()
return f"{y}/{m:02d}/{d:02d}"
except Exception:
return ''
def _render_template(template: CertificateTemplate, instance: ProcessInstance):
well = instance.well
rep = instance.representative
latest_report = InstallationReport.objects.filter(assignment__process_instance=instance).order_by('-created').first()
individual = True if rep.profile and rep.profile.user_type == 'individual' else False
customer_company_name = rep.profile.company_name if rep.profile and rep.profile.user_type == 'legal' else None
city = template.company.broker.affairs.county.city.name if template.company and template.company.broker and template.company.broker.affairs and template.company.broker.affairs.county and template.company.broker.affairs.county.city else None
county = template.company.broker.affairs.county.name if template.company and template.company.broker and template.company.broker.affairs and template.company.broker.affairs.county else None
ctx = {
'today_jalali': mark_safe(f"<span class=\"fw-bold\">{_to_jalali(timezone.now().date())}</span>"),
'request_code': mark_safe(f"<span class=\"fw-bold\">{instance.code}</span>"),
'company_name': mark_safe(f"<span class=\"fw-bold\">{(template.company.name if template.company else '') or ''}</span>"),
'customer_full_name': mark_safe(f"<span class=\"fw-bold\">{rep.get_full_name() if rep else ''}</span>"),
'water_subscription_number': mark_safe(f"<span class=\"fw-bold\">{getattr(well, 'water_subscription_number', '') or ''}</span>"),
'address': mark_safe(f"<span class=\"fw-bold\">{getattr(well, 'county', '') or ''}</span>"),
'visit_date_jalali': mark_safe(f"<span class=\"fw-bold\">{_to_jalali(getattr(latest_report, 'visited_date', None)) if latest_report else ''}</span>"),
'city': mark_safe(f"<span class=\"fw-bold\">{city or ''}</span>"),
'county': mark_safe(f"<span class=\"fw-bold\">{county or ''}</span>"),
'customer_company_name': mark_safe(f"<span class=\"fw-bold\">{customer_company_name or ''}</span>"),
'individual': individual,
}
# Render title using Django template engine
title_template = Template(template.title or '')
title = title_template.render(Context(ctx))
# Render body using Django template engine
body_template = Template(template.body or '')
body = body_template.render(Context(ctx))
return title, body
@login_required
def certificate_step(request, instance_id, step_id):
instance = get_scoped_instance_or_404(request, instance_id)
step = get_object_or_404(instance.process.steps, id=step_id)
# Ensure all previous steps are completed and invoice settled
prior_steps = instance.process.steps.filter(order__lt=instance.current_step.order if instance.current_step else 9999)
incomplete = StepInstance.objects.filter(process_instance=instance, step__in=prior_steps).exclude(status='completed').exists()
previous_step = instance.process.steps.filter(order__lt=instance.current_step.order).last() if instance.current_step else None
prev_si = StepInstance.objects.filter(process_instance=instance, step=previous_step).first() if previous_step else None
if incomplete and not prev_si.status == 'approved':
messages.error(request, 'ابتدا همه مراحل قبلی را تکمیل کنید')
return redirect('processes:request_list')
inv = Invoice.objects.filter(process_instance=instance).first()
if inv:
if prev_si and not prev_si.status == 'approved':
inv.calculate_totals()
if inv.get_remaining_amount() != 0:
messages.error(request, 'مانده فاکتور باید صفر باشد')
return redirect('processes:request_list')
template = CertificateTemplate.objects.filter(is_active=True).order_by('-created').first()
if not template:
return render(request, 'certificates/missing.html', {})
title, body = _render_template(template, instance)
cert, _ = CertificateInstance.objects.get_or_create(
process_instance=instance,
defaults={'template': template, 'rendered_title': title, 'rendered_body': body}
)
# keep rendered up-to-date
cert.template = template
cert.rendered_title = title
cert.rendered_body = body
cert.save()
previous_step = instance.process.steps.filter(order__lt=instance.current_step.order).last() if instance.current_step else None
next_step = instance.process.steps.filter(order__gt=instance.current_step.order).first() if instance.current_step else None
if request.method == 'POST':
# Only broker can approve and finish certificate step
try:
if not (hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.BROKER)):
messages.error(request, 'شما مجوز تایید این مرحله را ندارید')
return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
except Exception:
messages.error(request, 'شما مجوز تایید این مرحله را ندارید')
return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
# Safety check: ensure ALL previous steps are completed before approval
try:
prev_steps_qs = instance.process.steps.filter(order__lt=step.order)
has_incomplete = StepInstance.objects.filter(process_instance=instance, step__in=prev_steps_qs).exclude(status='completed').exists()
if has_incomplete:
messages.error(request, 'ابتدا همه مراحل قبلی را تکمیل کنید')
return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
except Exception:
messages.error(request, 'خطا در بررسی مراحل قبلی')
return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
cert.approved = True
cert.approved_at = timezone.now()
cert.save()
step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step_id=step_id)
step_instance.status = 'completed'
step_instance.completed_at = timezone.now()
step_instance.save()
if next_step:
instance.current_step = next_step
instance.save()
return redirect('processes:step_detail', instance_id=instance.id, step_id=next_step.id)
# Mark the whole process instance as completed on the last step
instance.status = 'completed'
instance.save()
return redirect('processes:instance_summary', instance_id=instance.id)
# latest installation report for details
latest_report = InstallationReport.objects.filter(assignment__process_instance=instance).order_by('-created').first()
return render(request, 'certificates/step.html', {
'instance': instance,
'template': template,
'cert': cert,
'previous_step': previous_step,
'next_step': next_step,
'step': step,
'latest_report': latest_report,
})
@login_required
@allowed_roles([UserRoles.BROKER, UserRoles.MANAGER])
def certificate_print(request, instance_id):
instance = get_scoped_instance_or_404(request, instance_id)
cert = CertificateInstance.objects.filter(process_instance=instance).order_by('-created').first()
latest_report = InstallationReport.objects.filter(assignment__process_instance=instance).order_by('-created').first()
if request.method == 'POST':
# Save/update hologram code then print
code = (request.POST.get('hologram_code') or '').strip()
if not code:
messages.error(request, 'کد یکتای هولوگرام الزامی است')
# Find certificate step to redirect back
certificate_step = instance.process.steps.filter(order=9).first()
if certificate_step and instance.current_step:
return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id)
return redirect('processes:instance_summary', instance_id=instance.id)
try:
if cert:
# Check if hologram code is already used by another certificate
if CertificateInstance.objects.filter(hologram_code=code).exclude(id=cert.id).exists():
messages.error(request, 'این کد هولوگرام قبلاً استفاده شده است. لطفاً کد دیگری وارد کنید')
# Find certificate step to redirect back
certificate_step = instance.process.steps.filter(order=9).first()
if certificate_step and instance.current_step:
return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id)
return redirect('processes:instance_summary', instance_id=instance.id)
cert.hologram_code = code
cert.save(update_fields=['hologram_code'])
else:
# Check if hologram code is already used
if CertificateInstance.objects.filter(hologram_code=code).exists():
messages.error(request, 'این کد هولوگرام قبلاً استفاده شده است. لطفاً کد دیگری وارد کنید')
# Find certificate step to redirect back
certificate_step = instance.process.steps.filter(order=9).first()
if certificate_step and instance.current_step:
return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id)
return redirect('processes:instance_summary', instance_id=instance.id)
template = CertificateTemplate.objects.filter(is_active=True).order_by('-created').first()
if template:
title, body = _render_template(template, instance)
cert = CertificateInstance.objects.create(
process_instance=instance,
template=template,
rendered_title=title,
rendered_body=body,
hologram_code=code
)
except IntegrityError:
messages.error(request, 'این کد هولوگرام قبلاً استفاده شده است. لطفاً کد دیگری وارد کنید')
# Find certificate step to redirect back
certificate_step = instance.process.steps.filter(order=9).first()
if certificate_step and instance.current_step:
return redirect('processes:step_detail', instance_id=instance.id, step_id=certificate_step.id)
return redirect('processes:instance_summary', instance_id=instance.id)
# proceed to rendering page after saving code
return render(request, 'certificates/print.html', {
'instance': instance,
'cert': cert,
'template': cert.template if cert else None,
'latest_report': latest_report,
})
template = cert.template if cert else None
return render(request, 'certificates/print.html', {
'instance': instance,
'cert': cert,
'template': template,
'latest_report': latest_report,
})