fix final payment step.
This commit is contained in:
		
							parent
							
								
									93db2fe7f5
								
							
						
					
					
						commit
						9592c00565
					
				
					 6 changed files with 305 additions and 80 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -1,55 +1,206 @@
 | 
				
			||||||
{% extends '_base.html' %}
 | 
					<!DOCTYPE html>
 | 
				
			||||||
{% load humanize %}
 | 
					<html lang="fa" dir="rtl">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					  <meta charset="utf-8">
 | 
				
			||||||
 | 
					  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					  <title>فاکتور نهایی {{ invoice.name }} - {{ instance.code }}</title>
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
{% block content %}
 | 
					  {% load static %}
 | 
				
			||||||
<div class="container py-4">
 | 
					  {% load humanize %}
 | 
				
			||||||
  <div class="mb-4 d-flex justify-content-between align-items-center">
 | 
					
 | 
				
			||||||
    <div>
 | 
					  <!-- Fonts (match base) -->
 | 
				
			||||||
      <h4 class="mb-1">فاکتور نهایی</h4>
 | 
					  <link rel="preconnect" href="https://fonts.googleapis.com">
 | 
				
			||||||
      <small class="text-muted">کد درخواست: {{ instance.code }}</small>
 | 
					  <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">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <!-- Icons (optional) -->
 | 
				
			||||||
 | 
					  <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/fonts/flag-icons.css' %}">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <!-- Core CSS (same as preview) -->
 | 
				
			||||||
 | 
					  <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: 1cm;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    @media print {
 | 
				
			||||||
 | 
					      body { print-color-adjust: exact; }
 | 
				
			||||||
 | 
					      .page-break { page-break-before: always; }
 | 
				
			||||||
 | 
					      .no-print { display: none !important; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .invoice-header { border-bottom: 1px solid #dee2e6; padding-bottom: 20px; margin-bottom: 30px; }
 | 
				
			||||||
 | 
					    .company-logo { font-size: 24px; font-weight: bold; color: #696cff; }
 | 
				
			||||||
 | 
					    .invoice-title { font-size: 28px; font-weight: bold; color: #333; }
 | 
				
			||||||
 | 
					    .info-table td { padding: 5px 10px; border: none; }
 | 
				
			||||||
 | 
					    .items-table { border: 1px solid #dee2e6; }
 | 
				
			||||||
 | 
					    .items-table th { background-color: #f8f9fa; border-bottom: 2px solid #dee2e6; font-weight: bold; text-align: center; }
 | 
				
			||||||
 | 
					    .items-table td { border-bottom: 1px solid #dee2e6; text-align: center; }
 | 
				
			||||||
 | 
					    .total-section { background-color: #f8f9fa; font-weight: bold; }
 | 
				
			||||||
 | 
					    .signature-section { margin-top: 50px; border-top: 1px solid #dee2e6; padding-top: 30px; }
 | 
				
			||||||
 | 
					    .signature-box { border: 1px dashed #ccc; height: 80px; text-align: center; display: flex; align-items: center; justify-content: center; color: #666; }
 | 
				
			||||||
 | 
					  </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					  <div class="container-fluid">
 | 
				
			||||||
 | 
					    <!-- Header -->
 | 
				
			||||||
 | 
					    <div class="invoice-header">
 | 
				
			||||||
 | 
					      <div class="row align-items-center">
 | 
				
			||||||
 | 
					        <div class="col-6 d-flex align-items-center">
 | 
				
			||||||
 | 
					          <div class="me-3" style="width:64px;height:64px;display:flex;align-items:center;justify-content:center;background:#eef2ff;border-radius:8px;">
 | 
				
			||||||
 | 
					            {% if instance.broker.company and instance.broker.company.logo %}
 | 
				
			||||||
 | 
					              <img src="{{ instance.broker.company.logo.url }}" alt="لوگو" style="max-height:58px;max-width:120px;">
 | 
				
			||||||
 | 
					            {% else %}
 | 
				
			||||||
 | 
					              <span class="company-logo">شرکت</span>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div>
 | 
					          <div>
 | 
				
			||||||
      <!-- Placeholders for logo/signature -->
 | 
					            {% if instance.broker.company %}
 | 
				
			||||||
      <div class="text-end">لوگو</div>
 | 
					              {{ instance.broker.company.name }}
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					            {% if instance.broker.company %}
 | 
				
			||||||
 | 
					              <div class="text-muted small">
 | 
				
			||||||
 | 
					                {% if instance.broker.company.address %}
 | 
				
			||||||
 | 
					                  <div>{{ instance.broker.company.address }}</div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if instance.broker.affairs.county.city.name %}
 | 
				
			||||||
 | 
					                  <div>{{ instance.broker.affairs.county.city.name }}، ایران</div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					                {% if instance.broker.company.phone %}
 | 
				
			||||||
 | 
					                  <div>تلفن: {{ instance.broker.company.phone }}</div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					              </div>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
  <div class="table-responsive">
 | 
					        <div class="col-6 text-end">
 | 
				
			||||||
    <table class="table table-bordered">
 | 
					          <div class="mt-2">
 | 
				
			||||||
 | 
					            <div><strong>#فاکتور نهایی {{ instance.code }}</strong></div>
 | 
				
			||||||
 | 
					            <div class="text-muted small">تاریخ صدور: {{ invoice.jcreated_date }}</div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Customer & Well Info -->
 | 
				
			||||||
 | 
					    <div class="row mb-3">
 | 
				
			||||||
 | 
					      <div class="col-6">
 | 
				
			||||||
 | 
					        <h6 class="fw-bold mb-2">اطلاعات مشترک</h6>
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">نام:</span> {{ invoice.customer.get_full_name|default:instance.representative.get_full_name }}</div>
 | 
				
			||||||
 | 
					        {% if instance.representative.profile and instance.representative.profile.national_code %}
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">کد ملی:</span> {{ instance.representative.profile.national_code }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {% if instance.representative.profile and instance.representative.profile.phone_number_1 %}
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">تلفن:</span> {{ instance.representative.profile.phone_number_1 }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {% if instance.representative.profile and instance.representative.profile.address %}
 | 
				
			||||||
 | 
					        <div class="small"><span class="text-muted">آدرس:</span> {{ instance.representative.profile.address }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div class="col-6">
 | 
				
			||||||
 | 
					        <h6 class="fw-bold mb-2">اطلاعات چاه</h6>
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">شماره اشتراک آب:</span> {{ instance.well.water_subscription_number }}</div>
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">شماره اشتراک برق:</span> {{ instance.well.electricity_subscription_number|default:"-" }}</div>
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">سریال کنتور:</span> {{ instance.well.water_meter_serial_number|default:"-" }}</div>
 | 
				
			||||||
 | 
					        <div class="small"><span class="text-muted">قدرت چاه:</span> {{ instance.well.well_power|default:"-" }}</div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <!-- Items Table -->
 | 
				
			||||||
 | 
					    <div class="mb-4">
 | 
				
			||||||
 | 
					      <table class="table border-top m-0 items-table">
 | 
				
			||||||
        <thead>
 | 
					        <thead>
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
          <th>آیتم</th>
 | 
					            <th style="width: 5%">ردیف</th>
 | 
				
			||||||
          <th>تعداد</th>
 | 
					            <th style="width: 30%">شرح کالا/خدمات</th>
 | 
				
			||||||
          <th>قیمت واحد</th>
 | 
					            <th style="width: 30%">توضیحات</th>
 | 
				
			||||||
          <th>قیمت کل</th>
 | 
					            <th style="width: 10%">تعداد</th>
 | 
				
			||||||
 | 
					            <th style="width: 12.5%">قیمت واحد(تومان)</th>
 | 
				
			||||||
 | 
					            <th style="width: 12.5%">قیمت کل(تومان)</th>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
        </thead>
 | 
					        </thead>
 | 
				
			||||||
        <tbody>
 | 
					        <tbody>
 | 
				
			||||||
          {% for it in items %}
 | 
					          {% for it in items %}
 | 
				
			||||||
          <tr>
 | 
					          <tr>
 | 
				
			||||||
          <td>{{ it.item.name }}</td>
 | 
					            <td>{{ forloop.counter }}</td>
 | 
				
			||||||
 | 
					            <td class="text-nowrap">{{ it.item.name }}</td>
 | 
				
			||||||
 | 
					            <td class="text-nowrap">{{ it.item.description|default:"-" }}</td>
 | 
				
			||||||
            <td>{{ it.quantity }}</td>
 | 
					            <td>{{ it.quantity }}</td>
 | 
				
			||||||
            <td>{{ it.unit_price|floatformat:0|intcomma:False }}</td>
 | 
					            <td>{{ it.unit_price|floatformat:0|intcomma:False }}</td>
 | 
				
			||||||
            <td>{{ it.total_price|floatformat:0|intcomma:False }}</td>
 | 
					            <td>{{ it.total_price|floatformat:0|intcomma:False }}</td>
 | 
				
			||||||
          </tr>
 | 
					          </tr>
 | 
				
			||||||
          {% empty %}
 | 
					          {% empty %}
 | 
				
			||||||
        <tr><td colspan="4" class="text-center text-muted">آیتمی ندارد</td></tr>
 | 
					          <tr><td colspan="6" class="text-center text-muted">آیتمی ندارد</td></tr>
 | 
				
			||||||
          {% endfor %}
 | 
					          {% endfor %}
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
        <tfoot>
 | 
					        <tfoot>
 | 
				
			||||||
        <tr><th colspan="3" class="text-end">مبلغ کل</th><th>{{ invoice.total_amount|floatformat:0|intcomma:False }}</th></tr>
 | 
					          <tr class="total-section">
 | 
				
			||||||
        <tr><th colspan="3" class="text-end">تخفیف</th><th>{{ invoice.discount_amount|floatformat:0|intcomma:False }}</th></tr>
 | 
					            <td colspan="5" class="text-end"><strong>جمع کل(تومان):</strong></td>
 | 
				
			||||||
        <tr><th colspan="3" class="text-end">مبلغ نهایی</th><th>{{ invoice.final_amount|floatformat:0|intcomma:False }}</th></tr>
 | 
					            <td><strong>{{ invoice.total_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
				
			||||||
        <tr><th colspan="3" class="text-end">پرداختیها</th><th>{{ invoice.paid_amount|floatformat:0|intcomma:False }}</th></tr>
 | 
					          </tr>
 | 
				
			||||||
        <tr><th colspan="3" class="text-end">مانده</th><th>{{ invoice.remaining_amount|floatformat:0|intcomma:False }}</th></tr>
 | 
					          {% if invoice.discount_amount > 0 %}
 | 
				
			||||||
 | 
					          <tr class="total-section">
 | 
				
			||||||
 | 
					            <td colspan="5" class="text-end"><strong>تخفیف(تومان):</strong></td>
 | 
				
			||||||
 | 
					            <td><strong>{{ invoice.discount_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					          {% endif %}
 | 
				
			||||||
 | 
					          <tr class="total-section border-top border-2">
 | 
				
			||||||
 | 
					            <td colspan="5" class="text-end"><strong>مبلغ نهایی(تومان):</strong></td>
 | 
				
			||||||
 | 
					            <td><strong>{{ invoice.final_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					          <tr class="total-section">
 | 
				
			||||||
 | 
					            <td colspan="5" class="text-end"><strong>پرداختیها(تومان):</strong></td>
 | 
				
			||||||
 | 
					            <td><strong">{{ invoice.paid_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
 | 
					          <tr class="total-section">
 | 
				
			||||||
 | 
					            <td colspan="5" class="text-end"><strong>مانده(تومان):</strong></td>
 | 
				
			||||||
 | 
					            <td><strong>{{ invoice.remaining_amount|floatformat:0|intcomma:False }}</strong></td>
 | 
				
			||||||
 | 
					          </tr>
 | 
				
			||||||
        </tfoot>
 | 
					        </tfoot>
 | 
				
			||||||
      </table>
 | 
					      </table>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  <div class="mt-5 d-flex justify-content-between">
 | 
					
 | 
				
			||||||
    <div>امضا مشتری</div>
 | 
					    <!-- Conditions & Payment -->
 | 
				
			||||||
    <div>امضا شرکت</div>
 | 
					    <div class="row">
 | 
				
			||||||
 | 
					      <div class="col-8">
 | 
				
			||||||
 | 
					        <h6 class="fw-bold">مهر و امضا:</h6>
 | 
				
			||||||
 | 
					        <ul class="small mb-0">
 | 
				
			||||||
 | 
					          {% if instance.broker.company and instance.broker.company.signature %}
 | 
				
			||||||
 | 
					          <li class="mt-3" style="list-style:none;"><img src="{{ instance.broker.company.signature.url }}" alt="امضا" style="height: 200px;"></li>
 | 
				
			||||||
 | 
					          {% endif %}
 | 
				
			||||||
 | 
					        </ul>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {% if instance.broker.company %}
 | 
				
			||||||
 | 
					      <div class="col-4">
 | 
				
			||||||
 | 
					        <h6 class="fw-bold mb-2">اطلاعات پرداخت</h6>
 | 
				
			||||||
 | 
					        {% if instance.broker.company.card_number %}
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">شماره کارت:</span> {{ instance.broker.company.card_number }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {% if instance.broker.company.account_number %}
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">شماره حساب:</span> {{ instance.broker.company.account_number }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {% if instance.broker.company.sheba_number %}
 | 
				
			||||||
 | 
					        <div class="small mb-1"><span class="text-muted">شماره شبا:</span> {{ instance.broker.company.sheba_number }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					        {% if instance.broker.company.bank_name %}
 | 
				
			||||||
 | 
					        <div class="small"><span class="text-muted">بانک:</span> {{ instance.broker.company.get_bank_name_display }}</div>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {% endif %}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
<script>window.print()</script>
 | 
					 | 
				
			||||||
{% endblock %}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <script>
 | 
				
			||||||
 | 
					    window.onload = function() {
 | 
				
			||||||
 | 
					      window.print();
 | 
				
			||||||
 | 
					      setTimeout(function(){ window.close(); }, 200);
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  </script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
{% include '_toasts.html' %}
 | 
					{% include '_toasts.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Instance Info Modal -->
 | 
				
			||||||
 | 
					{% instance_info_modal instance %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% csrf_token %}
 | 
					{% csrf_token %}
 | 
				
			||||||
<div class="container-xxl flex-grow-1 container-p-y">
 | 
					<div class="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
  <div class="row">
 | 
					  <div class="row">
 | 
				
			||||||
| 
						 | 
					@ -32,14 +36,18 @@
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
          <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
 | 
					          <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
 | 
				
			||||||
          <small class="text-muted d-block">
 | 
					          <small class="text-muted d-block">
 | 
				
			||||||
            اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }}
 | 
					            {% instance_info instance %}
 | 
				
			||||||
            | نماینده: {{ instance.representative.profile.national_code|default:"-" }}
 | 
					 | 
				
			||||||
          </small>
 | 
					          </small>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="d-flex gap-2">
 | 
					        <div class="d-flex gap-2">
 | 
				
			||||||
        <a href="{% url 'invoices:final_invoice_print' instance.id %}" target="_blank" class="btn btn-outline-secondary"><i class="bx bx-printer"></i> پرینت</a>
 | 
					        <a href="{% url 'invoices:final_invoice_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">بازگشت</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>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,15 +171,24 @@
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="card-footer d-flex justify-content-between">
 | 
					        <div class="card-footer d-flex justify-content-between">
 | 
				
			||||||
          {% if previous_step %}
 | 
					          {% 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 ms-sm-n2"></i>
 | 
				
			||||||
 | 
					              قبلی
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
          {% else %}
 | 
					          {% else %}
 | 
				
			||||||
            <span></span>
 | 
					            <span></span>
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
          {% if next_step %}
 | 
					          {% if next_step %}
 | 
				
			||||||
            {% if is_manager %}
 | 
					            {% if is_manager %}
 | 
				
			||||||
              <button type="button" class="btn btn-primary" id="btnApproveFinalInvoice">تایید و ادامه</button>
 | 
					              <button type="button" class="btn btn-primary" id="btnApproveFinalInvoice">
 | 
				
			||||||
 | 
					                تایید و ادامه
 | 
				
			||||||
 | 
					                <i class="bx bx-chevron-left bx-sm me-sm-n2"></i>
 | 
				
			||||||
 | 
					              </button>
 | 
				
			||||||
            {% else %}
 | 
					            {% 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 %}
 | 
					            {% endif %}
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
{% include '_toasts.html' %}
 | 
					{% include '_toasts.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Instance Info Modal -->
 | 
				
			||||||
 | 
					{% instance_info_modal instance %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% csrf_token %}
 | 
					{% csrf_token %}
 | 
				
			||||||
<div class="container-xxl flex-grow-1 container-p-y">
 | 
					<div class="container-xxl flex-grow-1 container-p-y">
 | 
				
			||||||
  <div class="row">
 | 
					  <div class="row">
 | 
				
			||||||
| 
						 | 
					@ -31,14 +35,18 @@
 | 
				
			||||||
        <div>
 | 
					        <div>
 | 
				
			||||||
          <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
 | 
					          <h4 class="mb-1">{{ step.name }}: {{ instance.process.name }}</h4>
 | 
				
			||||||
          <small class="text-muted d-block">
 | 
					          <small class="text-muted d-block">
 | 
				
			||||||
            اشتراک آب: {{ instance.well.water_subscription_number|default:"-" }}
 | 
					            {% instance_info instance %}
 | 
				
			||||||
            | نماینده: {{ instance.representative.profile.national_code|default:"-" }}
 | 
					 | 
				
			||||||
          </small>
 | 
					          </small>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="d-flex gap-2">
 | 
					        <div class="d-flex gap-2">
 | 
				
			||||||
          <a href="{% url 'invoices:final_invoice_print' instance.id %}" target="_blank" class="btn btn-outline-secondary"><i class="bx bx-printer"></i> پرینت</a>
 | 
					          <a href="{% url 'invoices:final_invoice_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">بازگشت</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>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,7 +96,7 @@
 | 
				
			||||||
                  <input type="file" class="form-control" name="receipt_image" id="id_receipt_image" accept="image/*" required>
 | 
					                  <input type="file" class="form-control" name="receipt_image" id="id_receipt_image" accept="image/*" required>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="d-flex justify-content-end">
 | 
					                <div class="d-flex justify-content-end">
 | 
				
			||||||
                  <button type="button" id="btnAddFinalPayment" class="btn btn-primary">افزودن</button>
 | 
					                  <button type="button" id="btnAddFinalPayment" class="btn btn-primary">افزودن فیش/چک</button>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </form>
 | 
					              </form>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
| 
						 | 
					@ -182,7 +190,7 @@
 | 
				
			||||||
          <h6 class="mb-0">وضعیت تاییدها</h6>
 | 
					          <h6 class="mb-0">وضعیت تاییدها</h6>
 | 
				
			||||||
          {% if can_approve_reject %}
 | 
					          {% if can_approve_reject %}
 | 
				
			||||||
          <div class="d-flex gap-2">
 | 
					          <div class="d-flex gap-2">
 | 
				
			||||||
            <button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#approveFinalSettleModal" {% if step_instance.status == 'completed' %}disabled{% endif %}>تایید</button>
 | 
					            <button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#approveFinalSettleModal">تایید</button>
 | 
				
			||||||
            <button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#rejectFinalSettleModal">رد</button>
 | 
					            <button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#rejectFinalSettleModal">رد</button>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
| 
						 | 
					@ -214,13 +222,19 @@
 | 
				
			||||||
      {% endif %}
 | 
					      {% endif %}
 | 
				
			||||||
      <div class="col-12 d-flex justify-content-between mt-3">
 | 
					      <div class="col-12 d-flex justify-content-between mt-3">
 | 
				
			||||||
        {% if previous_step %}
 | 
					        {% 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 ms-sm-n2"></i>
 | 
				
			||||||
 | 
					            قبلی
 | 
				
			||||||
 | 
					          </a>
 | 
				
			||||||
        {% else %}
 | 
					        {% else %}
 | 
				
			||||||
          <span></span>
 | 
					          <span></span>
 | 
				
			||||||
        {% endif %}
 | 
					        {% endif %}
 | 
				
			||||||
        {% if step_instance.status == 'completed' %}
 | 
					        {% if step_instance.status == 'completed' %}
 | 
				
			||||||
          {% if next_step %}
 | 
					          {% if next_step %}
 | 
				
			||||||
            <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>
 | 
				
			||||||
          {% else %}
 | 
					          {% else %}
 | 
				
			||||||
            <a href="{% url 'processes:request_list' %}" class="btn btn-success">اتمام</a>
 | 
					            <a href="{% url 'processes:request_list' %}" class="btn btn-success">اتمام</a>
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,5 +31,4 @@ urlpatterns = [
 | 
				
			||||||
    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/', views.final_settlement_step, name='final_settlement_step'),
 | 
					    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/', views.final_settlement_step, name='final_settlement_step'),
 | 
				
			||||||
    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/add/', views.add_final_payment, name='add_final_payment'),
 | 
					    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/add/', views.add_final_payment, name='add_final_payment'),
 | 
				
			||||||
    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/<int:payment_id>/delete/', views.delete_final_payment, name='delete_final_payment'),
 | 
					    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/<int:payment_id>/delete/', views.delete_final_payment, name='delete_final_payment'),
 | 
				
			||||||
    path('instance/<int:instance_id>/step/<int:step_id>/final-settlement/approve/', views.approve_final_settlement, name='approve_final_settlement'),
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,7 @@ import json
 | 
				
			||||||
from processes.models import ProcessInstance, ProcessStep, StepInstance, StepRejection, StepApproval
 | 
					from processes.models import ProcessInstance, ProcessStep, StepInstance, StepRejection, StepApproval
 | 
				
			||||||
from accounts.models import Role
 | 
					from accounts.models import Role
 | 
				
			||||||
from common.consts import UserRoles
 | 
					from common.consts import UserRoles
 | 
				
			||||||
from .models import Item, Quote, QuoteItem, Payment, Invoice
 | 
					from .models import Item, Quote, QuoteItem, Payment, Invoice, InvoiceItem
 | 
				
			||||||
from installations.models import InstallationReport, InstallationItemChange
 | 
					from installations.models import InstallationReport, InstallationItemChange
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -792,14 +792,7 @@ def approve_final_invoice(request, instance_id, step_id):
 | 
				
			||||||
            return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
 | 
					            return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
 | 
					        return JsonResponse({'success': False, 'message': 'شما مجوز تایید این مرحله را ندارید'}, status=403)
 | 
				
			||||||
    # Block approval when there is any remaining (positive or negative)
 | 
					
 | 
				
			||||||
    invoice.calculate_totals()
 | 
					 | 
				
			||||||
    # if invoice.remaining_amount != 0:
 | 
					 | 
				
			||||||
    #     return JsonResponse({
 | 
					 | 
				
			||||||
    #         'success': False,
 | 
					 | 
				
			||||||
    #         'message': f"تا زمانی که مانده فاکتور صفر نشده امکان تایید نیست (مانده فعلی: {invoice.remaining_amount})"
 | 
					 | 
				
			||||||
    #     })
 | 
					 | 
				
			||||||
    # mark step completed
 | 
					 | 
				
			||||||
    step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
					    step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
				
			||||||
    step_instance.status = 'completed'
 | 
					    step_instance.status = 'completed'
 | 
				
			||||||
    step_instance.completed_at = timezone.now()
 | 
					    step_instance.completed_at = timezone.now()
 | 
				
			||||||
| 
						 | 
					@ -826,7 +819,7 @@ def add_special_charge(request, instance_id, step_id):
 | 
				
			||||||
            return JsonResponse({'success': False, 'message': 'شما مجوز افزودن هزینه ویژه را ندارید'}, status=403)
 | 
					            return JsonResponse({'success': False, 'message': 'شما مجوز افزودن هزینه ویژه را ندارید'}, status=403)
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        return JsonResponse({'success': False, 'message': 'شما مجوز افزودن هزینه ویژه را ندارید'}, status=403)
 | 
					        return JsonResponse({'success': False, 'message': 'شما مجوز افزودن هزینه ویژه را ندارید'}, status=403)
 | 
				
			||||||
    # charge_type was removed from UI; we no longer require it
 | 
					
 | 
				
			||||||
    item_id = request.POST.get('item_id')
 | 
					    item_id = request.POST.get('item_id')
 | 
				
			||||||
    amount = (request.POST.get('amount') or '').strip()
 | 
					    amount = (request.POST.get('amount') or '').strip()
 | 
				
			||||||
    if not item_id:
 | 
					    if not item_id:
 | 
				
			||||||
| 
						 | 
					@ -841,7 +834,7 @@ def add_special_charge(request, instance_id, step_id):
 | 
				
			||||||
    # Fetch existing special item from DB
 | 
					    # Fetch existing special item from DB
 | 
				
			||||||
    special_item = get_object_or_404(Item, id=item_id, is_special=True)
 | 
					    special_item = get_object_or_404(Item, id=item_id, is_special=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    from .models import InvoiceItem
 | 
					    
 | 
				
			||||||
    InvoiceItem.objects.create(
 | 
					    InvoiceItem.objects.create(
 | 
				
			||||||
        invoice=invoice,
 | 
					        invoice=invoice,
 | 
				
			||||||
        item=special_item,
 | 
					        item=special_item,
 | 
				
			||||||
| 
						 | 
					@ -863,7 +856,6 @@ def delete_special_charge(request, instance_id, step_id, item_id):
 | 
				
			||||||
            return JsonResponse({'success': False, 'message': 'شما مجوز حذف هزینه ویژه را ندارید'}, status=403)
 | 
					            return JsonResponse({'success': False, 'message': 'شما مجوز حذف هزینه ویژه را ندارید'}, status=403)
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        return JsonResponse({'success': False, 'message': 'شما مجوز حذف هزینه ویژه را ندارید'}, status=403)
 | 
					        return JsonResponse({'success': False, 'message': 'شما مجوز حذف هزینه ویژه را ندارید'}, status=403)
 | 
				
			||||||
    from .models import InvoiceItem
 | 
					 | 
				
			||||||
    inv_item = get_object_or_404(InvoiceItem, id=item_id, invoice=invoice)
 | 
					    inv_item = get_object_or_404(InvoiceItem, id=item_id, invoice=invoice)
 | 
				
			||||||
    # allow deletion only for special items
 | 
					    # allow deletion only for special items
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
| 
						 | 
					@ -880,6 +872,7 @@ def delete_special_charge(request, instance_id, step_id, item_id):
 | 
				
			||||||
def final_settlement_step(request, instance_id, step_id):
 | 
					def final_settlement_step(request, instance_id, step_id):
 | 
				
			||||||
    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
					    instance = get_object_or_404(ProcessInstance, id=instance_id)
 | 
				
			||||||
    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
					    step = get_object_or_404(instance.process.steps, id=step_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not instance.can_access_step(step):
 | 
					    if not instance.can_access_step(step):
 | 
				
			||||||
        messages.error(request, 'شما به این مرحله دسترسی ندارید. ابتدا مراحل قبلی را تکمیل کنید.')
 | 
					        messages.error(request, 'شما به این مرحله دسترسی ندارید. ابتدا مراحل قبلی را تکمیل کنید.')
 | 
				
			||||||
        return redirect('processes:request_list')
 | 
					        return redirect('processes:request_list')
 | 
				
			||||||
| 
						 | 
					@ -890,6 +883,7 @@ def final_settlement_step(request, instance_id, step_id):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Ensure step instance exists
 | 
					    # Ensure step instance exists
 | 
				
			||||||
    step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'})
 | 
					    step_instance, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step, defaults={'status': 'in_progress'})
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    # Build approver statuses for template
 | 
					    # Build approver statuses for template
 | 
				
			||||||
    reqs = list(step.approver_requirements.select_related('role').all())
 | 
					    reqs = list(step.approver_requirements.select_related('role').all())
 | 
				
			||||||
    approvals_map = {a.role_id: a.decision for a in step_instance.approvals.select_related('role').all()}
 | 
					    approvals_map = {a.role_id: a.decision for a in step_instance.approvals.select_related('role').all()}
 | 
				
			||||||
| 
						 | 
					@ -947,6 +941,13 @@ def final_settlement_step(request, instance_id, step_id):
 | 
				
			||||||
                defaults={'approved_by': request.user, 'decision': 'rejected', 'reason': reason}
 | 
					                defaults={'approved_by': request.user, 'decision': 'rejected', 'reason': reason}
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            StepRejection.objects.create(step_instance=step_instance, rejected_by=request.user, reason=reason)
 | 
					            StepRejection.objects.create(step_instance=step_instance, rejected_by=request.user, reason=reason)
 | 
				
			||||||
 | 
					            # If current step is ahead of this step, reset it back to this step (align behavior with other steps)
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if instance.current_step and instance.current_step.order > step.order:
 | 
				
			||||||
 | 
					                    instance.current_step = step
 | 
				
			||||||
 | 
					                    instance.save(update_fields=['current_step'])
 | 
				
			||||||
 | 
					            except Exception:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
            messages.success(request, 'مرحله تسویه نهایی رد شد و برای اصلاح بازگشت.')
 | 
					            messages.success(request, 'مرحله تسویه نهایی رد شد و برای اصلاح بازگشت.')
 | 
				
			||||||
            return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
					            return redirect('invoices:final_settlement_step', instance_id=instance.id, step_id=step.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -984,6 +985,7 @@ def add_final_payment(request, instance_id, step_id):
 | 
				
			||||||
            return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
 | 
					            return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
 | 
					        return JsonResponse({'success': False, 'message': 'شما مجوز افزودن تراکنش تسویه را ندارید'}, status=403)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    amount = (request.POST.get('amount') or '').strip()
 | 
					    amount = (request.POST.get('amount') or '').strip()
 | 
				
			||||||
    payment_date = (request.POST.get('payment_date') or '').strip()
 | 
					    payment_date = (request.POST.get('payment_date') or '').strip()
 | 
				
			||||||
    payment_method = (request.POST.get('payment_method') or '').strip()
 | 
					    payment_method = (request.POST.get('payment_method') or '').strip()
 | 
				
			||||||
| 
						 | 
					@ -1038,12 +1040,14 @@ def add_final_payment(request, instance_id, step_id):
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    # After creation, totals auto-updated by model save. Respond with redirect and new totals for UX.
 | 
					    # After creation, totals auto-updated by model save. Respond with redirect and new totals for UX.
 | 
				
			||||||
    invoice.refresh_from_db()
 | 
					    invoice.refresh_from_db()
 | 
				
			||||||
    # After payment change, set step back to in_progress
 | 
					
 | 
				
			||||||
 | 
					    # On delete, return to awaiting approval
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
					        si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
				
			||||||
        si.status = 'in_progress'
 | 
					        si.status = 'in_progress'
 | 
				
			||||||
        si.completed_at = None
 | 
					        si.completed_at = None
 | 
				
			||||||
        si.save()
 | 
					        si.save()
 | 
				
			||||||
 | 
					        si.approvals.all().delete()
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					@ -1065,6 +1069,16 @@ def add_final_payment(request, instance_id, step_id):
 | 
				
			||||||
                    pass
 | 
					                    pass
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If current step is ahead of this step, reset it back to this step
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if instance.current_step and instance.current_step.order > step.order:
 | 
				
			||||||
 | 
					            instance.current_step = step
 | 
				
			||||||
 | 
					            instance.save(update_fields=['current_step'])
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return JsonResponse({
 | 
					    return JsonResponse({
 | 
				
			||||||
        'success': True,
 | 
					        'success': True,
 | 
				
			||||||
        'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]),
 | 
					        'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]),
 | 
				
			||||||
| 
						 | 
					@ -1091,14 +1105,44 @@ def delete_final_payment(request, instance_id, step_id, payment_id):
 | 
				
			||||||
        return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
 | 
					        return JsonResponse({'success': False, 'message': 'شما مجوز حذف تراکنش تسویه را ندارید'}, status=403)
 | 
				
			||||||
    payment.delete()
 | 
					    payment.delete()
 | 
				
			||||||
    invoice.refresh_from_db()
 | 
					    invoice.refresh_from_db()
 | 
				
			||||||
    # After payment change, set step back to in_progress
 | 
					    
 | 
				
			||||||
 | 
					    # On delete, return to awaiting approval
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
					        si, _ = StepInstance.objects.get_or_create(process_instance=instance, step=step)
 | 
				
			||||||
        si.status = 'in_progress'
 | 
					        si.status = 'in_progress'
 | 
				
			||||||
        si.completed_at = None
 | 
					        si.completed_at = None
 | 
				
			||||||
        si.save()
 | 
					        si.save()
 | 
				
			||||||
 | 
					        si.approvals.all().delete()
 | 
				
			||||||
    except Exception:
 | 
					    except Exception:
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Reset ALL subsequent completed steps to in_progress
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        subsequent_steps = instance.process.steps.filter(order__gt=step.order)
 | 
				
			||||||
 | 
					        for subsequent_step in subsequent_steps:
 | 
				
			||||||
 | 
					            subsequent_step_instance = instance.step_instances.filter(step=subsequent_step).first()
 | 
				
			||||||
 | 
					            if subsequent_step_instance and subsequent_step_instance.status == 'completed':
 | 
				
			||||||
 | 
					                # Bypass validation by using update() instead of save()
 | 
				
			||||||
 | 
					                instance.step_instances.filter(step=subsequent_step).update(
 | 
				
			||||||
 | 
					                    status='in_progress',
 | 
				
			||||||
 | 
					                    completed_at=None
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                # Clear previous approvals if the step requires re-approval
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    subsequent_step_instance.approvals.all().delete()
 | 
				
			||||||
 | 
					                except Exception:
 | 
				
			||||||
 | 
					                    pass
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If current step is ahead of this step, reset it back to this step
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if instance.current_step and instance.current_step.order > step.order:
 | 
				
			||||||
 | 
					            instance.current_step = step
 | 
				
			||||||
 | 
					            instance.save(update_fields=['current_step'])
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return JsonResponse({'success': True, 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), 'totals': {
 | 
					    return JsonResponse({'success': True, 'redirect': reverse('invoices:final_settlement_step', args=[instance.id, step_id]), 'totals': {
 | 
				
			||||||
        'final_amount': str(invoice.final_amount),
 | 
					        'final_amount': str(invoice.final_amount),
 | 
				
			||||||
        'paid_amount': str(invoice.paid_amount),
 | 
					        'paid_amount': str(invoice.paid_amount),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue