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"{_to_jalali(timezone.now().date())}"), 'request_code': mark_safe(f"{instance.code}"), 'company_name': mark_safe(f"{(template.company.name if template.company else '') or ''}"), 'customer_full_name': mark_safe(f"{rep.get_full_name() if rep else ''}"), 'water_subscription_number': mark_safe(f"{getattr(well, 'water_subscription_number', '') or ''}"), 'address': mark_safe(f"{getattr(well, 'county', '') or ''}"), 'visit_date_jalali': mark_safe(f"{_to_jalali(getattr(latest_report, 'visited_date', None)) if latest_report else ''}"), 'city': mark_safe(f"{city or ''}"), 'county': mark_safe(f"{county or ''}"), 'customer_company_name': mark_safe(f"{customer_company_name or ''}"), '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, })