fix date in payment and imporove contract page.
This commit is contained in:
		
							parent
							
								
									af40e169ae
								
							
						
					
					
						commit
						204b0aa48e
					
				
					 14 changed files with 295 additions and 74 deletions
				
			
		| 
						 | 
				
			
			@ -2,45 +2,71 @@
 | 
			
		|||
<html lang="fa" dir="rtl">
 | 
			
		||||
<head>
 | 
			
		||||
  <meta charset="utf-8">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
			
		||||
  <title>چاپ قرارداد {{ instance.code }}</title>
 | 
			
		||||
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
 | 
			
		||||
  {% load static %}
 | 
			
		||||
 | 
			
		||||
  <!-- Match app fonts and theme -->
 | 
			
		||||
  <link rel="preconnect" href="https://fonts.googleapis.com">
 | 
			
		||||
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 | 
			
		||||
  <link href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500,1,600,1,700&display=swap" rel="stylesheet">
 | 
			
		||||
  <link rel="stylesheet" href="{% static 'assets/vendor/fonts/boxicons.css' %}">
 | 
			
		||||
  <link rel="stylesheet" href="{% static 'assets/vendor/fonts/fontawesome.css' %}">
 | 
			
		||||
  <link rel="stylesheet" href="{% static 'assets/vendor/css/rtl/core.css' %}">
 | 
			
		||||
  <link rel="stylesheet" href="{% static 'assets/vendor/css/rtl/theme-default.css' %}">
 | 
			
		||||
  <link rel="stylesheet" href="{% static 'assets/css/demo.css' %}">
 | 
			
		||||
  <link rel="stylesheet" href="{% static 'assets/css/persian-fonts.css' %}">
 | 
			
		||||
 | 
			
		||||
  <style>
 | 
			
		||||
    @page { size: A4; margin: 1.2cm; }
 | 
			
		||||
    body { font-family: 'Vazirmatn', sans-serif; }
 | 
			
		||||
    .logo { max-height: 80px; }
 | 
			
		||||
    .signature { height: 90px; border: 1px dashed #ccc; }
 | 
			
		||||
    .invoice-header { border-bottom: 1px solid #dee2e6; padding-bottom: 16px; margin-bottom: 24px; }
 | 
			
		||||
    .brand-box { width:64px; height:64px; display:flex; align-items:center; justify-content:center; background:#eef2ff; border-radius:8px; }
 | 
			
		||||
    .logo { max-height: 58px; max-width: 120px; }
 | 
			
		||||
    .contract-title { font-size: 20px; font-weight: 600; }
 | 
			
		||||
    .small-muted { font-size: 12px; color: #6c757d; }
 | 
			
		||||
    .signature-box { border: 1px dashed #ccc; height: 210px; display:flex; align-items:center; justify-content:center; }
 | 
			
		||||
  </style>
 | 
			
		||||
  <script>
 | 
			
		||||
    window.addEventListener('load', function(){ setTimeout(function(){ window.print(); }, 300); });
 | 
			
		||||
    window.onload = function(){
 | 
			
		||||
      window.print();
 | 
			
		||||
      setTimeout(function(){ window.close(); }, 200);
 | 
			
		||||
    };
 | 
			
		||||
  </script>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <div class="container-fluid">
 | 
			
		||||
    <div class="d-flex justify-content-between align-items-center mb-3">
 | 
			
		||||
      <div>
 | 
			
		||||
        <h5>{{ contract.template.company.name }}</h5>
 | 
			
		||||
        <h5 class="mb-1">{{ contract.template.name }}</h5>
 | 
			
		||||
        <div class="text-muted small">کد درخواست: {{ instance.code }} | تاریخ: {{ contract.jcreated }}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
      {% if contract.template.company.logo %}
 | 
			
		||||
        <img class="logo" src="{{ contract.template.company.logo.url }}" alt="لوگو" />
 | 
			
		||||
      {% endif %}
 | 
			
		||||
      
 | 
			
		||||
    <!-- Header: Company and contract info -->
 | 
			
		||||
    <div class="invoice-header">
 | 
			
		||||
      <div class="small text-end text-muted mb-2">تاریخ: {{ contract.jcreated_date }} | کد درخواست: {{ instance.code }}</div>
 | 
			
		||||
      <h5 class="text-center mb-3">
 | 
			
		||||
        {% if instance.broker and instance.broker.company %}
 | 
			
		||||
          {{ instance.broker.company.name }}
 | 
			
		||||
        {% elif template.company %}
 | 
			
		||||
          {{ template.company.name }}
 | 
			
		||||
        {% else %}
 | 
			
		||||
          شرکت آب منطقهای
 | 
			
		||||
        {% endif %}
 | 
			
		||||
      </h5>
 | 
			
		||||
      <h4 class="text-center mb-3">{{ contract.template.name }}</h4>
 | 
			
		||||
    </div>
 | 
			
		||||
    <hr>
 | 
			
		||||
 | 
			
		||||
    <!-- Contract body -->
 | 
			
		||||
    <div style="white-space: pre-line; line-height: 1.9;">{{ contract.rendered_body|safe }}</div>
 | 
			
		||||
    <hr>
 | 
			
		||||
 | 
			
		||||
    <!-- Signatures -->
 | 
			
		||||
    <div class="row mt-4">
 | 
			
		||||
      <div class="col-6 text-center">
 | 
			
		||||
        <div>امضای مشترک</div>
 | 
			
		||||
        <div class="signature mt-2"></div>
 | 
			
		||||
        <div class="signature-box mt-2"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="col-6 text-center">
 | 
			
		||||
        <div>امضای شرکت</div>
 | 
			
		||||
        <div class="signature mt-2">
 | 
			
		||||
          {% if contract.template.company.signature %}
 | 
			
		||||
            <img src="{{ contract.template.company.signature.url }}" alt="امضای شرکت" style="max-height: 80px;" />
 | 
			
		||||
        <div class="signature-box mt-2">
 | 
			
		||||
          {% if instance.broker and instance.broker.company and instance.broker.company.signature %}
 | 
			
		||||
            <img src="{{ instance.broker.company.signature.url }}" alt="امضای شرکت" style="max-height: 200px;" />
 | 
			
		||||
          {% elif contract.template.company and contract.template.company.signature %}
 | 
			
		||||
            <img src="{{ contract.template.company.signature.url }}" alt="امضای شرکت" style="max-height: 200px;" />
 | 
			
		||||
          {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,10 @@
 | 
			
		|||
 | 
			
		||||
{% block content %}
 | 
			
		||||
{% include '_toasts.html' %}
 | 
			
		||||
 | 
			
		||||
<!-- Instance Info Modal -->
 | 
			
		||||
{% instance_info_modal instance %}
 | 
			
		||||
 | 
			
		||||
<div class="container-xxl flex-grow-1 container-p-y">
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col-12 mb-4">
 | 
			
		||||
| 
						 | 
				
			
			@ -26,13 +30,18 @@
 | 
			
		|||
        <div>
 | 
			
		||||
          <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
 | 
			
		||||
          <small class="text-muted d-block">
 | 
			
		||||
            اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }}
 | 
			
		||||
            | نماینده: {{ instance.representative.profile.national_code|default:"-" }}
 | 
			
		||||
            {% instance_info instance %}
 | 
			
		||||
          </small>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="d-flex gap-2">
 | 
			
		||||
          <a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">بازگشت</a>
 | 
			
		||||
          <a href="{% url 'contracts:contract_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">پرینت</a>
 | 
			
		||||
          <a href="{% url 'contracts:contract_print' instance.id %}" target="_blank" class="btn btn-outline-secondary">
 | 
			
		||||
            <i class="bx bx-printer me-2"></i> پرینت
 | 
			
		||||
          </a>
 | 
			
		||||
          <a href="{% url 'processes:request_list' %}" class="btn btn-outline-secondary">
 | 
			
		||||
            <i class="bx bx-chevron-right bx-sm ms-sm-n2"></i>
 | 
			
		||||
            بازگشت
 | 
			
		||||
          </a>
 | 
			
		||||
          
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,29 +50,32 @@
 | 
			
		|||
        <div class="bs-stepper-content">
 | 
			
		||||
          <div class="card border">
 | 
			
		||||
            <div class="card-body">
 | 
			
		||||
              <div class="small text-end text-muted mb-2">تاریخ: {{ contract.jcreated_date }} | کد درخواست: {{ instance.code }}</div>
 | 
			
		||||
              <h5 class="text-center mb-3">
 | 
			
		||||
                {% if instance.broker and instance.broker.company %}
 | 
			
		||||
                {{ instance.broker.company.name }}
 | 
			
		||||
              {% elif template.company %}
 | 
			
		||||
                {{ template.company.name }}
 | 
			
		||||
              {% else %}
 | 
			
		||||
                شرکت آب منطقهای
 | 
			
		||||
              {% endif %}</h5>
 | 
			
		||||
              <h4 class="text-center mb-3">{{ contract.template.name }}</h4>
 | 
			
		||||
              {% if can_view_contract_body %}
 | 
			
		||||
                {% if template.company.logo %}
 | 
			
		||||
                  <div class="text-center mb-3">
 | 
			
		||||
                    <img src="{{ template.company.logo.url }}" alt="لوگوی شرکت" style="max-height:80px;">
 | 
			
		||||
                    <h4 class="text-muted">{{ contract.template.company.name }}</h4>
 | 
			
		||||
                    <h5 class="text-muted">{{ contract.template.name }}</h5>
 | 
			
		||||
                  </div>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
 | 
			
		||||
                <div class="small text-muted mb-2">تاریخ: {{ contract.jcreated }}</div>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <div class="contract-body" style="white-space: pre-line; line-height:1.9;">{{ contract.rendered_body|safe }}</div>
 | 
			
		||||
                <hr>
 | 
			
		||||
                <div class="row mt-4">
 | 
			
		||||
                  <div class="col-6 text-center">
 | 
			
		||||
                    <div>امضای مشترک</div>
 | 
			
		||||
                    <div style="height:90px;border:1px dashed #ccc; margin-top:10px;"></div>
 | 
			
		||||
                    <div style="height:210px;border:1px dashed #ccc; margin-top:10px;"></div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="col-6 text-center">
 | 
			
		||||
                    <div>امضای شرکت</div>
 | 
			
		||||
                    <div style="height:90px;border:1px dashed #ccc; margin-top:10px;">
 | 
			
		||||
                      {% if template.company.signature %}
 | 
			
		||||
                        <img src="{{ template.company.signature.url }}" alt="امضای شرکت" style="max-height:80px;">
 | 
			
		||||
                    <div style="height:210px;border:1px dashed #ccc; margin-top:10px;">
 | 
			
		||||
                      {% if instance.broker and instance.broker.company and instance.broker.company.signature %}
 | 
			
		||||
                        <img src="{{ instance.broker.company.signature.url }}" alt="امضای شرکت" style="max-height:200px;">
 | 
			
		||||
                      {% elif template.company and template.company.signature %}
 | 
			
		||||
                        <img src="{{ template.company.signature.url }}" alt="امضای شرکت" style="max-height:200px;">
 | 
			
		||||
                      {% endif %}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -76,15 +88,23 @@
 | 
			
		|||
          <form method="post" class="d-flex justify-content-between mt-3">
 | 
			
		||||
            {% csrf_token %}
 | 
			
		||||
            {% if previous_step %}
 | 
			
		||||
              <a href="{% url 'processes:step_detail' instance.id previous_step.id %}" class="btn btn-label-secondary">قبلی</a>
 | 
			
		||||
              <a href="{% url 'processes:step_detail' instance.id previous_step.id %}" class="btn btn-label-secondary">
 | 
			
		||||
                <i class="bx bx-chevron-right bx-sm me-sm-2"></i>
 | 
			
		||||
                قبلی
 | 
			
		||||
              </a>
 | 
			
		||||
            {% else %}
 | 
			
		||||
              <span></span>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% if next_step %}
 | 
			
		||||
              {% if is_broker %}
 | 
			
		||||
                <button type="submit" class="btn btn-primary">تایید و بعدی</button>
 | 
			
		||||
                <button type="submit" class="btn btn-primary">تایید و بعدی
 | 
			
		||||
                  <i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
 | 
			
		||||
                </button>
 | 
			
		||||
              {% else %}
 | 
			
		||||
              <a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-primary">بعدی</a>
 | 
			
		||||
              <a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-primary">
 | 
			
		||||
                بعدی
 | 
			
		||||
                <i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
 | 
			
		||||
              </a>
 | 
			
		||||
              {% endif %}
 | 
			
		||||
            {% else %}
 | 
			
		||||
              {% if is_broker %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,29 +2,51 @@ from django.shortcuts import render, get_object_or_404, redirect
 | 
			
		|||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from decimal import Decimal
 | 
			
		||||
from django.template import Template, Context
 | 
			
		||||
from django.utils.safestring import mark_safe
 | 
			
		||||
from processes.models import ProcessInstance, StepInstance
 | 
			
		||||
from common.consts import UserRoles
 | 
			
		||||
from .models import ContractTemplate, ContractInstance
 | 
			
		||||
from invoices.models import Invoice, Quote
 | 
			
		||||
from _helpers.utils import jalali_converter2
 | 
			
		||||
from django.http import JsonResponse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def build_contract_context(instance: ProcessInstance) -> dict:
 | 
			
		||||
    representative = instance.representative
 | 
			
		||||
    profile = getattr(representative, 'profile', None)
 | 
			
		||||
    well = instance.well
 | 
			
		||||
    # Compute prepayment from Quote-linked invoice payments
 | 
			
		||||
    quote = Quote.objects.filter(process_instance=instance).first()
 | 
			
		||||
    invoice = Invoice.objects.filter(quote=quote).first() if quote else None
 | 
			
		||||
    payments_qs = invoice.payments.filter(is_deleted=False, direction='in').all() if invoice else []
 | 
			
		||||
    total_paid = sum((p.amount for p in payments_qs), Decimal('0'))
 | 
			
		||||
    try:
 | 
			
		||||
        latest_payment_date = max((p.payment_date for p in payments_qs)) if payments_qs else None
 | 
			
		||||
    except Exception:
 | 
			
		||||
        latest_payment_date = None
 | 
			
		||||
    
 | 
			
		||||
    return {
 | 
			
		||||
        'customer_full_name': representative.get_full_name() if representative else '',
 | 
			
		||||
        'national_code': profile.national_code if profile else '',
 | 
			
		||||
        'address': profile.address if profile else '',
 | 
			
		||||
        'phone': profile.phone_number_1 if profile else '',
 | 
			
		||||
        'phone2': profile.phone_number_2 if profile else '',
 | 
			
		||||
        'water_subscription_number': well.water_subscription_number if well else '',
 | 
			
		||||
        'electricity_subscription_number': well.electricity_subscription_number if well else '',
 | 
			
		||||
        'water_meter_serial_number': well.water_meter_serial_number if well else '',
 | 
			
		||||
        'well_power': well.well_power if well else '',
 | 
			
		||||
        'request_code': instance.code,
 | 
			
		||||
        'today': jalali_converter2(timezone.now()),
 | 
			
		||||
        'customer_full_name': mark_safe(f"<span class=\"fw-bold\">{representative.get_full_name() if representative else ''}</span>"),
 | 
			
		||||
        'registration_number': mark_safe(f"<span class=\"fw-bold\">{instance.broker.company.registration_number if instance.broker and instance.broker.company else ''}</span>"),
 | 
			
		||||
        'national_code': mark_safe(f"<span class=\"fw-bold\">{profile.national_code if profile else ''}</span>"),
 | 
			
		||||
        'address': mark_safe(f"<span class=\"fw-bold\">{profile.address if profile else ''}</span>"),
 | 
			
		||||
        'phone': mark_safe(f"<span class=\"fw-bold\">{profile.phone_number_1 if profile else ''}</span>"),
 | 
			
		||||
        'phone2': mark_safe(f"<span class=\"fw-bold\">{profile.phone_number_2 if profile else ''}</span>"),
 | 
			
		||||
        'water_subscription_number': mark_safe(f"<span class=\"fw-bold\">{well.water_subscription_number if well else ''}</span>"),
 | 
			
		||||
        'electricity_subscription_number': mark_safe(f"<span class=\"fw-bold\">{well.electricity_subscription_number if well else ''}</span>"),
 | 
			
		||||
        'water_meter_serial_number': mark_safe(f"<span class=\"fw-bold\">{well.water_meter_serial_number if well else ''}</span>"),
 | 
			
		||||
        'well_power': mark_safe(f"<span class=\"fw-bold\">{well.well_power if well else ''}</span>"),
 | 
			
		||||
        'request_code': mark_safe(f"<span class=\"fw-bold\">{instance.code}</span>"),
 | 
			
		||||
        'today': mark_safe(f"<span class=\"fw-bold\">{jalali_converter2(timezone.now())}</span>"),
 | 
			
		||||
        'company_name': mark_safe(f"<span class=\"fw-bold\">{instance.broker.company.name if instance.broker and instance.broker.company else ''}</span>"),
 | 
			
		||||
        'city_name': mark_safe(f"<span class=\"fw-bold\">{instance.broker.affairs.county.city.name if instance.broker and instance.broker.affairs and instance.broker.affairs.county and instance.broker.affairs.county.city else ''}</span>"),
 | 
			
		||||
        'card_number': mark_safe(f"<span class=\"fw-bold\">{instance.representative.profile.card_number if instance.representative else ''}</span>"),
 | 
			
		||||
        'account_number': mark_safe(f"<span class=\"fw-bold\">{instance.representative.profile.account_number if instance.representative else ''}</span>"),
 | 
			
		||||
        'bank_name': mark_safe(f"<span class=\"fw-bold\">{instance.representative.profile.get_bank_name_display() if instance.representative else ''}</span>"),
 | 
			
		||||
        'prepayment_amount': mark_safe(f"<span class=\"fw-bold\">{int(total_paid):,}</span>"),
 | 
			
		||||
        'prepayment_date': mark_safe(f"<span class=\"fw-bold\">{jalali_converter2(latest_payment_date)}</span>") if latest_payment_date else '',
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,10 +57,7 @@ def contract_step(request, instance_id, step_id):
 | 
			
		|||
    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
			
		||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
			
		||||
    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
			
		||||
    # Access control:
 | 
			
		||||
    # - INSTALLER: can open step but cannot view contract body (show inline message)
 | 
			
		||||
    # - Others: can view
 | 
			
		||||
    # - Only BROKER can submit/complete this step
 | 
			
		||||
 | 
			
		||||
    profile = getattr(request.user, 'profile', None)
 | 
			
		||||
    is_broker = False
 | 
			
		||||
    can_view_contract_body = True
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +91,6 @@ def contract_step(request, instance_id, step_id):
 | 
			
		|||
    # If user submits to go next, only broker can complete and go to next
 | 
			
		||||
    if request.method == 'POST':
 | 
			
		||||
        if not is_broker:
 | 
			
		||||
            from django.http import JsonResponse
 | 
			
		||||
            return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
 | 
			
		||||
        StepInstance.objects.update_or_create(
 | 
			
		||||
            process_instance=instance,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue