226 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			226 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 _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
 | 
						|
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,
 | 
						|
    })
 | 
						|
 | 
						|
 |