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 _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 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, })