fix installation step and utm standarded.
This commit is contained in:
		
							parent
							
								
									204b0aa48e
								
							
						
					
					
						commit
						93db2fe7f5
					
				
					 10 changed files with 191 additions and 108 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db.sqlite3
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -42,8 +42,8 @@ class InstallationReport(BaseModel):
 | 
				
			||||||
    new_water_meter_serial = models.CharField(max_length=50, null=True, blank=True, verbose_name='سریال کنتور جدید')
 | 
					    new_water_meter_serial = models.CharField(max_length=50, null=True, blank=True, verbose_name='سریال کنتور جدید')
 | 
				
			||||||
    seal_number = models.CharField(max_length=50, null=True, blank=True, verbose_name='شماره پلمپ')
 | 
					    seal_number = models.CharField(max_length=50, null=True, blank=True, verbose_name='شماره پلمپ')
 | 
				
			||||||
    is_meter_suspicious = models.BooleanField(default=False, verbose_name='کنتور مشکوک است؟')
 | 
					    is_meter_suspicious = models.BooleanField(default=False, verbose_name='کنتور مشکوک است؟')
 | 
				
			||||||
    utm_x = models.DecimalField(max_digits=10, decimal_places=6, null=True, blank=True, verbose_name='UTM X')
 | 
					    utm_x = models.DecimalField(max_digits=10, decimal_places=0, null=True, blank=True, verbose_name='UTM X')
 | 
				
			||||||
    utm_y = models.DecimalField(max_digits=10, decimal_places=6, null=True, blank=True, verbose_name='UTM Y')
 | 
					    utm_y = models.DecimalField(max_digits=10, decimal_places=0, null=True, blank=True, verbose_name='UTM Y')
 | 
				
			||||||
    description = models.TextField(blank=True, verbose_name='توضیحات')
 | 
					    description = models.TextField(blank=True, verbose_name='توضیحات')
 | 
				
			||||||
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='ایجادکننده')
 | 
					    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='ایجادکننده')
 | 
				
			||||||
    approved = models.BooleanField(default=False, verbose_name='تایید شده')
 | 
					    approved = models.BooleanField(default=False, verbose_name='تایید شده')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,12 @@
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% include '_toasts.html' %}
 | 
					{% include '_toasts.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Instance Info Modal -->
 | 
				
			||||||
 | 
					{% instance_info_modal instance %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<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">
 | 
				
			||||||
    <div class="col-12 mb-4">
 | 
					    <div class="col-12 mb-4">
 | 
				
			||||||
| 
						 | 
					@ -30,11 +35,13 @@
 | 
				
			||||||
        <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>
 | 
				
			||||||
        <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 class="bs-stepper wizard-vertical vertical mt-2">
 | 
					      <div class="bs-stepper wizard-vertical vertical mt-2">
 | 
				
			||||||
| 
						 | 
					@ -64,17 +71,17 @@
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            {% if assignment.assigned_by or assignment.installer %}
 | 
					            {% if assignment.assigned_by or assignment.installer %}
 | 
				
			||||||
            <div class="mt-3 border rounded p-3 bg-light">
 | 
					            <div class="mt-3 alert alert-primary">
 | 
				
			||||||
              <div class="row g-2">
 | 
					              <div class="row g-2">
 | 
				
			||||||
                {% if assignment.assigned_by %}
 | 
					                {% if assignment.assigned_by %}
 | 
				
			||||||
                <div class="col-12 col-md-6">
 | 
					                <div class="col-12 col-md-6">
 | 
				
			||||||
                  <div class="small text-muted">تعیینکننده نصاب</div>
 | 
					                  <div class="small text-dark">تعیینکننده نصاب</div>
 | 
				
			||||||
                  <div>{{ assignment.assigned_by.get_full_name|default:assignment.assigned_by.username }} <span class="text-muted">({{ assignment.assigned_by.username }})</span></div>
 | 
					                  <div>{{ assignment.assigned_by.get_full_name|default:assignment.assigned_by.username }} <span class="text-muted">({{ assignment.assigned_by.username }})</span></div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                {% if assignment.updated %}
 | 
					                {% if assignment.updated %}
 | 
				
			||||||
                <div class="col-12 col-md-6">
 | 
					                <div class="col-12 col-md-6">
 | 
				
			||||||
                  <div class="small text-muted">تاریخ ثبت/ویرایش</div>
 | 
					                  <div class="small text-dark">تاریخ ثبت/ویرایش</div>
 | 
				
			||||||
                  <div>{{ assignment.updated|to_jalali }}</div>
 | 
					                  <div>{{ assignment.updated|to_jalali }}</div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
| 
						 | 
					@ -83,14 +90,22 @@
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
            <div class="d-flex justify-content-between mt-4">
 | 
					            <div class="d-flex justify-content-between mt-4">
 | 
				
			||||||
              {% 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 is_manager %}
 | 
					              {% if is_manager %}
 | 
				
			||||||
                <button class="btn btn-primary" type="submit">ثبت و ادامه</button>
 | 
					                <button class="btn btn-primary" type="submit">ثبت و ادامه
 | 
				
			||||||
 | 
					                  <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 %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </form>
 | 
					          </form>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,7 +35,12 @@
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% include '_toasts.html' %}
 | 
					{% include '_toasts.html' %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<!-- Instance Info Modal -->
 | 
				
			||||||
 | 
					{% instance_info_modal instance %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<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">
 | 
				
			||||||
    <div class="col-12 mb-4">
 | 
					    <div class="col-12 mb-4">
 | 
				
			||||||
| 
						 | 
					@ -43,11 +48,13 @@
 | 
				
			||||||
        <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>
 | 
				
			||||||
        <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 class="bs-stepper wizard-vertical vertical mt-2">
 | 
					      <div class="bs-stepper wizard-vertical vertical mt-2">
 | 
				
			||||||
| 
						 | 
					@ -55,18 +62,15 @@
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        <div class="bs-stepper-content">
 | 
					        <div class="bs-stepper-content">
 | 
				
			||||||
          {% if report and not edit_mode %}
 | 
					          {% if report and not edit_mode %}
 | 
				
			||||||
          <div class="card mb-3 border">
 | 
					          <div class="mb-3 text-end">
 | 
				
			||||||
            <div class="card-header d-flex justify-content-between align-items-center">
 | 
					            {% if user_is_installer %}
 | 
				
			||||||
              <div class="d-flex gap-2">
 | 
					              <a href="?edit=1" class="btn btn-primary">
 | 
				
			||||||
                {% if request.user|is_installer %}
 | 
					                <i class="bx bx-edit bx-sm me-2"></i>
 | 
				
			||||||
                  <a href="?edit=1" class="btn btn-primary">ویرایش گزارش نصب</a>
 | 
					                ویرایش گزارش نصب
 | 
				
			||||||
                {% else %}
 | 
					              </a>
 | 
				
			||||||
                  <button type="button" class="btn btn-primary" disabled>ویرایش گزارش نصب</button>
 | 
					            {% endif %}
 | 
				
			||||||
                {% endif %}
 | 
					          </div>
 | 
				
			||||||
              </div>
 | 
					          {% if step_instance and step_instance.status == 'rejected' and step_instance.get_latest_rejection %}
 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
            <div class="card-body">
 | 
					 | 
				
			||||||
              {% if step_instance and step_instance.status == 'rejected' and step_instance.get_latest_rejection %}
 | 
					 | 
				
			||||||
              <div class="alert alert-danger d-flex align-items-start" role="alert">
 | 
					              <div class="alert alert-danger d-flex align-items-start" role="alert">
 | 
				
			||||||
                <i class="bx bx-error-circle me-2"></i>
 | 
					                <i class="bx bx-error-circle me-2"></i>
 | 
				
			||||||
                <div>
 | 
					                <div>
 | 
				
			||||||
| 
						 | 
					@ -75,6 +79,8 @@
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              {% endif %}
 | 
					              {% endif %}
 | 
				
			||||||
 | 
					          <div class="card mb-3 border">
 | 
				
			||||||
 | 
					            <div class="card-body">
 | 
				
			||||||
              <div class="row">
 | 
					              <div class="row">
 | 
				
			||||||
                <div class="col-md-6">
 | 
					                <div class="col-md-6">
 | 
				
			||||||
                  <p class="text-nowrap mb-2"><i class="bx bx-calendar-event bx-sm me-2"></i>تاریخ مراجعه: {{ report.visited_date|to_jalali|default:'-' }}</p>
 | 
					                  <p class="text-nowrap mb-2"><i class="bx bx-calendar-event bx-sm me-2"></i>تاریخ مراجعه: {{ report.visited_date|to_jalali|default:'-' }}</p>
 | 
				
			||||||
| 
						 | 
					@ -151,7 +157,7 @@
 | 
				
			||||||
              <h6 class="mb-0">وضعیت تاییدها</h6>
 | 
					              <h6 class="mb-0">وضعیت تاییدها</h6>
 | 
				
			||||||
              {% if user_can_approve %}
 | 
					              {% if user_can_approve %}
 | 
				
			||||||
              <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="#approveModal" {% if step_instance and step_instance.status == 'completed' %}disabled{% endif %}>تایید</button>
 | 
					                <button type="button" class="btn btn-success btn-sm" data-bs-toggle="modal" data-bs-target="#approveModal">تایید</button>
 | 
				
			||||||
                <button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#rejectModal">رد</button>
 | 
					                <button type="button" class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#rejectModal">رد</button>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              {% endif %}
 | 
					              {% endif %}
 | 
				
			||||||
| 
						 | 
					@ -184,18 +190,28 @@
 | 
				
			||||||
          <!-- Persistent nav in edit mode (outside cards) -->
 | 
					          <!-- Persistent nav in edit mode (outside cards) -->
 | 
				
			||||||
          <div class="d-flex justify-content-between mt-3">
 | 
					          <div class="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 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>
 | 
				
			||||||
            {% endif %}
 | 
					            {% endif %}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          {% else %}
 | 
					          {% else %}
 | 
				
			||||||
          {% if not request.user|is_installer %}
 | 
					
 | 
				
			||||||
          <div class="alert alert-warning">شما مجوز ثبت/ویرایش گزارش نصب را ندارید. اطلاعات به صورت فقط خواندنی نمایش داده میشود.</div>
 | 
					          {% if not user_is_installer %}
 | 
				
			||||||
 | 
					          <div class="alert alert-warning">شما مجوز ثبت/ویرایش گزارش نصب را ندارید.</div>
 | 
				
			||||||
          {% endif %}
 | 
					          {% endif %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          {% if user_is_installer %}
 | 
				
			||||||
 | 
					          <!-- Installation Report Form -->
 | 
				
			||||||
          <form method="post" enctype="multipart/form-data" id="installation-report-form">
 | 
					          <form method="post" enctype="multipart/form-data" id="installation-report-form">
 | 
				
			||||||
            {% csrf_token %}
 | 
					            {% csrf_token %}
 | 
				
			||||||
            <div class="mb-3">
 | 
					            <div class="mb-3">
 | 
				
			||||||
| 
						 | 
					@ -203,40 +219,40 @@
 | 
				
			||||||
                <div class="row g-3">
 | 
					                <div class="row g-3">
 | 
				
			||||||
                  <div class="col-md-3">
 | 
					                  <div class="col-md-3">
 | 
				
			||||||
                    <label class="form-label">تاریخ مراجعه</label>
 | 
					                    <label class="form-label">تاریخ مراجعه</label>
 | 
				
			||||||
                    <input type="text" id="id_visited_date_display" class="form-control" placeholder="انتخاب تاریخ" {% if not request.user|is_installer %}disabled{% endif %} readonly required value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y/m/d' }}{% endif %}">
 | 
					                    <input type="text" id="id_visited_date_display" class="form-control" placeholder="انتخاب تاریخ" {% if not user_is_installer %}disabled{% endif %} readonly required value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y/m/d' }}{% endif %}">
 | 
				
			||||||
                    <input type="hidden" id="id_visited_date" name="visited_date" value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y-m-d' }}{% endif %}">
 | 
					                    <input type="hidden" id="id_visited_date" name="visited_date" value="{% if report and edit_mode and report.visited_date %}{{ report.visited_date|date:'Y-m-d' }}{% endif %}">
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <div class="col-md-3">
 | 
					                  <div class="col-md-3">
 | 
				
			||||||
                    <label class="form-label">سریال کنتور جدید</label>
 | 
					                    <label class="form-label">سریال کنتور جدید</label>
 | 
				
			||||||
                    <input type="text" class="form-control" name="new_water_meter_serial" value="{% if report and edit_mode %}{{ report.new_water_meter_serial|default_if_none:'' }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
 | 
					                    <input type="text" class="form-control" name="new_water_meter_serial" value="{% if report and edit_mode %}{{ report.new_water_meter_serial|default_if_none:'' }}{% endif %}" {% if not user_is_installer %}readonly{% endif %}>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <div class="col-md-3">
 | 
					                  <div class="col-md-3">
 | 
				
			||||||
                    <label class="form-label">شماره پلمپ</label>
 | 
					                    <label class="form-label">شماره پلمپ</label>
 | 
				
			||||||
                    <input type="text" class="form-control" name="seal_number" value="{% if report and edit_mode %}{{ report.seal_number|default_if_none:'' }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
 | 
					                    <input type="text" class="form-control" name="seal_number" value="{% if report and edit_mode %}{{ report.seal_number|default_if_none:'' }}{% endif %}" {% if not user_is_installer %}readonly{% endif %}>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <div class="col-md-3 d-flex align-items-end">
 | 
					                  <div class="col-md-3 d-flex align-items-end">
 | 
				
			||||||
                    <div class="form-check">
 | 
					                    <div class="form-check">
 | 
				
			||||||
                      <input class="form-check-input" type="checkbox" name="is_meter_suspicious" id="id_is_meter_suspicious" {% if not request.user|is_installer %}disabled{% endif %} {% if report and edit_mode and report.is_meter_suspicious %}checked{% endif %}>
 | 
					                      <input class="form-check-input" type="checkbox" name="is_meter_suspicious" id="id_is_meter_suspicious" {% if not user_is_installer %}disabled{% endif %} {% if report and edit_mode and report.is_meter_suspicious %}checked{% endif %}>
 | 
				
			||||||
                      <label class="form-check-label" for="id_is_meter_suspicious">کنتور مشکوک است</label>
 | 
					                      <label class="form-check-label" for="id_is_meter_suspicious">کنتور مشکوک است</label>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <div class="col-md-3">
 | 
					                  <div class="col-md-3">
 | 
				
			||||||
                    <label class="form-label">UTM X</label>
 | 
					                    <label class="form-label">UTM X</label>
 | 
				
			||||||
                    <input type="number" step="0.000001" class="form-control" name="utm_x" value="{% if report and edit_mode and report.utm_x %}{{ report.utm_x }}{% elif instance.well.utm_x %}{{ instance.well.utm_x }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
 | 
					                    <input type="number" step="1" class="form-control" name="utm_x" value="{% if report and edit_mode and report.utm_x %}{{ report.utm_x }}{% elif instance.well.utm_x %}{{ instance.well.utm_x }}{% endif %}" {% if not user_is_installer %}readonly{% endif %}>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                  <div class="col-md-3">
 | 
					                  <div class="col-md-3">
 | 
				
			||||||
                    <label class="form-label">UTM Y</label>
 | 
					                    <label class="form-label">UTM Y</label>
 | 
				
			||||||
                    <input type="number" step="0.000001" class="form-control" name="utm_y" value="{% if report and edit_mode and report.utm_y %}{{ report.utm_y }}{% elif instance.well.utm_y %}{{ instance.well.utm_y }}{% endif %}" {% if not request.user|is_installer %}readonly{% endif %}>
 | 
					                    <input type="number" step="1" class="form-control" name="utm_y" value="{% if report and edit_mode and report.utm_y %}{{ report.utm_y }}{% elif instance.well.utm_y %}{{ instance.well.utm_y }}{% endif %}" {% if not user_is_installer %}readonly{% endif %}>
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="my-3">
 | 
					                <div class="my-3">
 | 
				
			||||||
                  <label class="form-label">توضیحات (اختیاری)</label>
 | 
					                  <label class="form-label">توضیحات (اختیاری)</label>
 | 
				
			||||||
                  <textarea class="form-control" rows="3" name="description" {% if not request.user|is_installer %}readonly{% endif %}>{% if report and edit_mode %}{{ report.description|default_if_none:'' }}{% endif %}</textarea>
 | 
					                  <textarea class="form-control" rows="3" name="description" {% if not user_is_installer %}readonly{% endif %}>{% if report and edit_mode %}{{ report.description|default_if_none:'' }}{% endif %}</textarea>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class="mb-3">
 | 
					                <div class="mb-3">
 | 
				
			||||||
                  <div class="d-flex justify-content-between align-items-center">
 | 
					                  <div class="d-flex justify-content-between align-items-center">
 | 
				
			||||||
                    <label class="form-label mb-0">عکسها</label>
 | 
					                    <label class="form-label mb-0">عکسها</label>
 | 
				
			||||||
                    {% if request.user|is_installer %}
 | 
					                    {% if user_is_installer %}
 | 
				
			||||||
                      <button type="button" class="btn btn-sm btn-outline-primary" id="btnAddPhoto"><i class="bx bx-plus"></i> افزودن عکس</button>
 | 
					                      <button type="button" class="btn btn-sm btn-outline-primary" id="btnAddPhoto"><i class="bx bx-plus"></i> افزودن عکس</button>
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                  </div>
 | 
					                  </div>
 | 
				
			||||||
| 
						 | 
					@ -246,7 +262,7 @@
 | 
				
			||||||
                      <div class="col-6 col-md-3 mb-2" id="existing-photo-{{ p.id }}">
 | 
					                      <div class="col-6 col-md-3 mb-2" id="existing-photo-{{ p.id }}">
 | 
				
			||||||
                        <div class="position-relative border rounded p-1">
 | 
					                        <div class="position-relative border rounded p-1">
 | 
				
			||||||
                          <img class="img-fluid rounded" src="{{ p.image.url }}" alt="photo">
 | 
					                          <img class="img-fluid rounded" src="{{ p.image.url }}" alt="photo">
 | 
				
			||||||
                          {% if request.user|is_installer %}
 | 
					                          {% if user_is_installer %}
 | 
				
			||||||
                            <button type="button" class="btn btn-sm btn-danger position-absolute" style="top:6px; left:6px;" onclick="markDeletePhoto('{{ p.id }}')" title="حذف/برگردان"><i class="bx bx-trash"></i></button>
 | 
					                            <button type="button" class="btn btn-sm btn-danger position-absolute" style="top:6px; left:6px;" onclick="markDeletePhoto('{{ p.id }}')" title="حذف/برگردان"><i class="bx bx-trash"></i></button>
 | 
				
			||||||
                          {% endif %}
 | 
					                          {% endif %}
 | 
				
			||||||
                          <input type="hidden" name="del_photo_{{ p.id }}" id="del-photo-{{ p.id }}" value="0">
 | 
					                          <input type="hidden" name="del_photo_{{ p.id }}" id="del-photo-{{ p.id }}" value="0">
 | 
				
			||||||
| 
						 | 
					@ -350,21 +366,25 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </form>
 | 
					          </form>
 | 
				
			||||||
 | 
					          {% endif %}
 | 
				
			||||||
          <div class="mt-3 d-flex justify-content-between">
 | 
					          <div class="mt-3 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 %}
 | 
				
			||||||
            <div class="d-flex gap-2">
 | 
					            <div class="d-flex gap-2">
 | 
				
			||||||
              {% if request.user|is_installer %}
 | 
					              {% if user_is_installer %}
 | 
				
			||||||
                <button type="submit" class="btn btn-primary" form="installation-report-form">ثبت گزارش</button>
 | 
					                <button type="submit" class="btn btn-success" form="installation-report-form">ثبت گزارش</button>
 | 
				
			||||||
              {% else %}
 | 
					 | 
				
			||||||
                <button type="button" class="btn btn-primary" disabled>ثبت گزارش</button>
 | 
					 | 
				
			||||||
              {% endif %}
 | 
					              {% endif %}
 | 
				
			||||||
              {% if next_step %}
 | 
					              {% if next_step %}
 | 
				
			||||||
                <a href="{% url 'processes:step_detail' instance.id next_step.id %}" class="btn btn-success">بعدی</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 %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
| 
						 | 
					@ -499,7 +519,6 @@
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      if (sessionStorage.getItem('install_report_saved') === '1') {
 | 
					      if (sessionStorage.getItem('install_report_saved') === '1') {
 | 
				
			||||||
        sessionStorage.removeItem('install_report_saved');
 | 
					        sessionStorage.removeItem('install_report_saved');
 | 
				
			||||||
        showToast('گزارش نصب با موفقیت ثبت شد', 'success');
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch(_) {}
 | 
					    } catch(_) {}
 | 
				
			||||||
  })();
 | 
					  })();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ def installation_assign_step(request, instance_id, step_id):
 | 
				
			||||||
    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
					    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Installers list (profiles that have installer role)
 | 
					    # Installers list (profiles that have installer role)
 | 
				
			||||||
    installers = Profile.objects.filter(roles__slug=UserRoles.INSTALLER.value).select_related('user').all()
 | 
					    installers = Profile.objects.filter(roles__slug=UserRoles.INSTALLER.value, county=instance.well.county).select_related('user').all()
 | 
				
			||||||
    assignment, _ = InstallationAssignment.objects.get_or_create(process_instance=instance)
 | 
					    assignment, _ = InstallationAssignment.objects.get_or_create(process_instance=instance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Role flags
 | 
					    # Role flags
 | 
				
			||||||
| 
						 | 
					@ -72,17 +72,56 @@ def installation_assign_step(request, instance_id, step_id):
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_item_changes_for_report(report, remove_map, add_map, quote_price_map):
 | 
				
			||||||
 | 
					    """Helper function to create item changes for a report"""
 | 
				
			||||||
 | 
					    # Create remove changes
 | 
				
			||||||
 | 
					    for item_id, qty in remove_map.items():
 | 
				
			||||||
 | 
					        up = quote_price_map.get(item_id)
 | 
				
			||||||
 | 
					        total = (up * qty) if up is not None else None
 | 
				
			||||||
 | 
					        InstallationItemChange.objects.create(
 | 
				
			||||||
 | 
					            report=report,
 | 
				
			||||||
 | 
					            item_id=item_id,
 | 
				
			||||||
 | 
					            change_type='remove',
 | 
				
			||||||
 | 
					            quantity=qty,
 | 
				
			||||||
 | 
					            unit_price=up,
 | 
				
			||||||
 | 
					            total_price=total,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Create add changes
 | 
				
			||||||
 | 
					    for item_id, data in add_map.items():
 | 
				
			||||||
 | 
					        unit_price = data.get('price')
 | 
				
			||||||
 | 
					        qty = data.get('qty') or 1
 | 
				
			||||||
 | 
					        total = (unit_price * qty) if (unit_price is not None) else None
 | 
				
			||||||
 | 
					        InstallationItemChange.objects.create(
 | 
				
			||||||
 | 
					            report=report,
 | 
				
			||||||
 | 
					            item_id=item_id,
 | 
				
			||||||
 | 
					            change_type='add',
 | 
				
			||||||
 | 
					            quantity=qty,
 | 
				
			||||||
 | 
					            unit_price=unit_price,
 | 
				
			||||||
 | 
					            total_price=total,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def installation_report_step(request, instance_id, step_id):
 | 
					def installation_report_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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
					    previous_step = instance.process.steps.filter(order__lt=step.order).last()
 | 
				
			||||||
    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
					    next_step = instance.process.steps.filter(order__gt=step.order).first()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    assignment = InstallationAssignment.objects.filter(process_instance=instance).first()
 | 
					    assignment = InstallationAssignment.objects.filter(process_instance=instance).first()
 | 
				
			||||||
    existing_report = InstallationReport.objects.filter(assignment=assignment).order_by('-created').first()
 | 
					    existing_report = InstallationReport.objects.filter(assignment=assignment).order_by('-created').first()
 | 
				
			||||||
    # Only installers can enter edit mode
 | 
					
 | 
				
			||||||
    user_is_installer = hasattr(request.user, 'profile') and request.user.profile.has_role(UserRoles.INSTALLER)
 | 
					    # Only the assigned installer can create/edit the report
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        has_installer_role = bool(getattr(request.user, 'profile', None) and request.user.profile.has_role(UserRoles.INSTALLER))
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        has_installer_role = False
 | 
				
			||||||
 | 
					    is_assigned_installer = bool(assignment and assignment.installer_id == request.user.id)
 | 
				
			||||||
 | 
					    user_is_installer = bool(has_installer_role and is_assigned_installer)
 | 
				
			||||||
    edit_mode = True if (request.GET.get('edit') == '1' and user_is_installer) else False
 | 
					    edit_mode = True if (request.GET.get('edit') == '1' and user_is_installer) else False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # current quote items baseline
 | 
					    # current quote items baseline
 | 
				
			||||||
    quote = Quote.objects.filter(process_instance=instance).first()
 | 
					    quote = Quote.objects.filter(process_instance=instance).first()
 | 
				
			||||||
    quote_items = list(quote.items.select_related('item').all()) if quote else []
 | 
					    quote_items = list(quote.items.select_related('item').all()) if quote else []
 | 
				
			||||||
| 
						 | 
					@ -100,7 +139,14 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
    reqs = list(step.approver_requirements.select_related('role').all())
 | 
					    reqs = list(step.approver_requirements.select_related('role').all())
 | 
				
			||||||
    user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', None)
 | 
					    user_roles_qs = getattr(getattr(request.user, 'profile', None), 'roles', None)
 | 
				
			||||||
    user_roles = list(user_roles_qs.all()) if user_roles_qs is not None else []
 | 
					    user_roles = list(user_roles_qs.all()) if user_roles_qs is not None else []
 | 
				
			||||||
    user_can_approve = any(r.role in user_roles for r in reqs)
 | 
					    # Align permission check with invoices flow (role id intersection)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        req_role_ids = {r.role_id for r in reqs}
 | 
				
			||||||
 | 
					        user_role_ids = {ur.id for ur in user_roles}
 | 
				
			||||||
 | 
					        can_approve_reject = len(req_role_ids.intersection(user_role_ids)) > 0
 | 
				
			||||||
 | 
					    except Exception:
 | 
				
			||||||
 | 
					        can_approve_reject = False
 | 
				
			||||||
 | 
					    user_can_approve = can_approve_reject
 | 
				
			||||||
    approvals_list = list(step_instance.approvals.select_related('role').all())
 | 
					    approvals_list = list(step_instance.approvals.select_related('role').all())
 | 
				
			||||||
    approvals_by_role = {a.role_id: a for a in approvals_list}
 | 
					    approvals_by_role = {a.role_id: a for a in approvals_list}
 | 
				
			||||||
    approver_statuses = [
 | 
					    approver_statuses = [
 | 
				
			||||||
| 
						 | 
					@ -160,6 +206,13 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
            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)
 | 
				
			||||||
            existing_report.approved = False
 | 
					            existing_report.approved = False
 | 
				
			||||||
            existing_report.save()
 | 
					            existing_report.save()
 | 
				
			||||||
 | 
					            # If current step moved ahead of this step, reset it back for correction (align with invoices)
 | 
				
			||||||
 | 
					            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('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
					            return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -177,6 +230,21 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
        is_suspicious = True if request.POST.get('is_meter_suspicious') == 'on' else False
 | 
					        is_suspicious = True if request.POST.get('is_meter_suspicious') == 'on' else False
 | 
				
			||||||
        utm_x = request.POST.get('utm_x') or None
 | 
					        utm_x = request.POST.get('utm_x') or None
 | 
				
			||||||
        utm_y = request.POST.get('utm_y') or None
 | 
					        utm_y = request.POST.get('utm_y') or None
 | 
				
			||||||
 | 
					        # Normalize UTM to integer meters
 | 
				
			||||||
 | 
					        if utm_x is not None and utm_x != '':
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                utm_x = int(Decimal(str(utm_x)))
 | 
				
			||||||
 | 
					            except InvalidOperation:
 | 
				
			||||||
 | 
					                utm_x = None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            utm_x = None
 | 
				
			||||||
 | 
					        if utm_y is not None and utm_y != '':
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                utm_y = int(Decimal(str(utm_y)))
 | 
				
			||||||
 | 
					            except InvalidOperation:
 | 
				
			||||||
 | 
					                utm_y = None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            utm_y = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Build maps from form fields: remove and add
 | 
					        # Build maps from form fields: remove and add
 | 
				
			||||||
        remove_map = {}
 | 
					        remove_map = {}
 | 
				
			||||||
| 
						 | 
					@ -221,8 +289,6 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
                    unit_price = item_obj.unit_price if item_obj else None
 | 
					                    unit_price = item_obj.unit_price if item_obj else None
 | 
				
			||||||
                add_map[item_id] = {'qty': qty, 'price': unit_price}
 | 
					                add_map[item_id] = {'qty': qty, 'price': unit_price}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # اجازهٔ ثبت همزمان حذف و افزودن برای یک قلم (بدون محدودیت و ادغام)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if existing_report and edit_mode:
 | 
					        if existing_report and edit_mode:
 | 
				
			||||||
            report = existing_report
 | 
					            report = existing_report
 | 
				
			||||||
            report.description = description
 | 
					            report.description = description
 | 
				
			||||||
| 
						 | 
					@ -247,29 +313,7 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
                InstallationPhoto.objects.create(report=report, image=f)
 | 
					                InstallationPhoto.objects.create(report=report, image=f)
 | 
				
			||||||
            # replace item changes with new submission
 | 
					            # replace item changes with new submission
 | 
				
			||||||
            report.item_changes.all().delete()
 | 
					            report.item_changes.all().delete()
 | 
				
			||||||
            for item_id, qty in remove_map.items():
 | 
					            create_item_changes_for_report(report, remove_map, add_map, quote_price_map)
 | 
				
			||||||
                up = quote_price_map.get(item_id)
 | 
					 | 
				
			||||||
                total = (up * qty) if up is not None else None
 | 
					 | 
				
			||||||
                InstallationItemChange.objects.create(
 | 
					 | 
				
			||||||
                    report=report,
 | 
					 | 
				
			||||||
                    item_id=item_id,
 | 
					 | 
				
			||||||
                    change_type='remove',
 | 
					 | 
				
			||||||
                    quantity=qty,
 | 
					 | 
				
			||||||
                    unit_price=up,
 | 
					 | 
				
			||||||
                    total_price=total,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            for item_id, data in add_map.items():
 | 
					 | 
				
			||||||
                unit_price = data.get('price')
 | 
					 | 
				
			||||||
                qty = data.get('qty') or 1
 | 
					 | 
				
			||||||
                total = (unit_price * qty) if (unit_price is not None) else None
 | 
					 | 
				
			||||||
                InstallationItemChange.objects.create(
 | 
					 | 
				
			||||||
                    report=report,
 | 
					 | 
				
			||||||
                    item_id=item_id,
 | 
					 | 
				
			||||||
                    change_type='add',
 | 
					 | 
				
			||||||
                    quantity=qty,
 | 
					 | 
				
			||||||
                    unit_price=unit_price,
 | 
					 | 
				
			||||||
                    total_price=total,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            report = InstallationReport.objects.create(
 | 
					            report = InstallationReport.objects.create(
 | 
				
			||||||
                assignment=assignment,
 | 
					                assignment=assignment,
 | 
				
			||||||
| 
						 | 
					@ -286,29 +330,7 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
            for f in request.FILES.getlist('photos'):
 | 
					            for f in request.FILES.getlist('photos'):
 | 
				
			||||||
                InstallationPhoto.objects.create(report=report, image=f)
 | 
					                InstallationPhoto.objects.create(report=report, image=f)
 | 
				
			||||||
            # item changes
 | 
					            # item changes
 | 
				
			||||||
            for item_id, qty in remove_map.items():
 | 
					            create_item_changes_for_report(report, remove_map, add_map, quote_price_map)
 | 
				
			||||||
                up = quote_price_map.get(item_id)
 | 
					 | 
				
			||||||
                total = (up * qty) if up is not None else None
 | 
					 | 
				
			||||||
                InstallationItemChange.objects.create(
 | 
					 | 
				
			||||||
                    report=report,
 | 
					 | 
				
			||||||
                    item_id=item_id,
 | 
					 | 
				
			||||||
                    change_type='remove',
 | 
					 | 
				
			||||||
                    quantity=qty,
 | 
					 | 
				
			||||||
                    unit_price=up,
 | 
					 | 
				
			||||||
                    total_price=total,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            for item_id, data in add_map.items():
 | 
					 | 
				
			||||||
                unit_price = data.get('price')
 | 
					 | 
				
			||||||
                qty = data.get('qty') or 1
 | 
					 | 
				
			||||||
                total = (unit_price * qty) if (unit_price is not None) else None
 | 
					 | 
				
			||||||
                InstallationItemChange.objects.create(
 | 
					 | 
				
			||||||
                    report=report,
 | 
					 | 
				
			||||||
                    item_id=item_id,
 | 
					 | 
				
			||||||
                    change_type='add',
 | 
					 | 
				
			||||||
                    quantity=qty,
 | 
					 | 
				
			||||||
                    unit_price=unit_price,
 | 
					 | 
				
			||||||
                    total_price=total,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # After installer submits/edits, set step back to in_progress and clear approvals
 | 
					        # After installer submits/edits, set step back to in_progress and clear approvals
 | 
				
			||||||
        step_instance.status = 'in_progress'
 | 
					        step_instance.status = 'in_progress'
 | 
				
			||||||
| 
						 | 
					@ -319,6 +341,33 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
        except Exception:
 | 
					        except Exception:
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If the report was edited, ensure downstream steps reopen like invoices flow
 | 
				
			||||||
 | 
					        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':
 | 
				
			||||||
 | 
					                    # Reopen the step
 | 
				
			||||||
 | 
					                    instance.step_instances.filter(step=subsequent_step).update(
 | 
				
			||||||
 | 
					                        status='in_progress',
 | 
				
			||||||
 | 
					                        completed_at=None
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    # Clear previous approvals if any
 | 
				
			||||||
 | 
					                    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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        messages.success(request, 'گزارش ثبت شد و در انتظار تایید است.')
 | 
					        messages.success(request, 'گزارش ثبت شد و در انتظار تایید است.')
 | 
				
			||||||
        return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
					        return redirect('processes:step_detail', instance_id=instance.id, step_id=step.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -340,6 +389,7 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
        'assignment': assignment,
 | 
					        'assignment': assignment,
 | 
				
			||||||
        'report': existing_report,
 | 
					        'report': existing_report,
 | 
				
			||||||
        'edit_mode': edit_mode,
 | 
					        'edit_mode': edit_mode,
 | 
				
			||||||
 | 
					        'user_is_installer': user_is_installer,
 | 
				
			||||||
        'quote': quote,
 | 
					        'quote': quote,
 | 
				
			||||||
        'quote_items': quote_items,
 | 
					        'quote_items': quote_items,
 | 
				
			||||||
        'all_items': items,
 | 
					        'all_items': items,
 | 
				
			||||||
| 
						 | 
					@ -351,6 +401,7 @@ def installation_report_step(request, instance_id, step_id):
 | 
				
			||||||
        'step_instance': step_instance,
 | 
					        'step_instance': step_instance,
 | 
				
			||||||
        'approver_statuses': approver_statuses,
 | 
					        'approver_statuses': approver_statuses,
 | 
				
			||||||
        'user_can_approve': user_can_approve,
 | 
					        'user_can_approve': user_can_approve,
 | 
				
			||||||
 | 
					        'can_approve_reject': can_approve_reject,
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -408,7 +408,7 @@ def quote_payment_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
 | 
					            # If current step is ahead of this step, reset it back to this step
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                if instance.current_step and instance.current_step.order > step.order:
 | 
					                if instance.current_step and instance.current_step.order > step.order:
 | 
				
			||||||
                    instance.current_step = step
 | 
					                    instance.current_step = step
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ class City(NameSlugModel):
 | 
				
			||||||
        return self.name
 | 
					        return self.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class County(NameSlugModel):
 | 
					class County(NameSlugModel):
 | 
				
			||||||
    city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name="شهرستان")
 | 
					    city = models.ForeignKey(City, on_delete=models.CASCADE, verbose_name="استان")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        verbose_name = "شهرستان"
 | 
					        verbose_name = "شهرستان"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -479,7 +479,7 @@
 | 
				
			||||||
    $('#requests-table').DataTable({
 | 
					    $('#requests-table').DataTable({
 | 
				
			||||||
      pageLength: 10,
 | 
					      pageLength: 10,
 | 
				
			||||||
      lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "همه"]],
 | 
					      lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "همه"]],
 | 
				
			||||||
      order: [[0, 'desc']],
 | 
					      order: [],
 | 
				
			||||||
      responsive: true,
 | 
					      responsive: true,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    let currentWellId = null;
 | 
					    let currentWellId = null;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -82,12 +82,10 @@ class WellForm(forms.ModelForm):
 | 
				
			||||||
            'utm_x': forms.NumberInput(attrs={
 | 
					            'utm_x': forms.NumberInput(attrs={
 | 
				
			||||||
                'class': 'form-control',
 | 
					                'class': 'form-control',
 | 
				
			||||||
                'placeholder': 'X UTM',
 | 
					                'placeholder': 'X UTM',
 | 
				
			||||||
                'step': '0.000001'
 | 
					 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
            'utm_y': forms.NumberInput(attrs={
 | 
					            'utm_y': forms.NumberInput(attrs={
 | 
				
			||||||
                'class': 'form-control',
 | 
					                'class': 'form-control',
 | 
				
			||||||
                'placeholder': 'Y UTM',
 | 
					                'placeholder': 'Y UTM',
 | 
				
			||||||
                'step': '0.000001'
 | 
					 | 
				
			||||||
            }),
 | 
					            }),
 | 
				
			||||||
            'utm_zone': forms.NumberInput(attrs={
 | 
					            'utm_zone': forms.NumberInput(attrs={
 | 
				
			||||||
                'class': 'form-control',
 | 
					                'class': 'form-control',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,14 +78,14 @@ class Well(SluggedModel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    utm_x = models.DecimalField(
 | 
					    utm_x = models.DecimalField(
 | 
				
			||||||
        max_digits=10,
 | 
					        max_digits=10,
 | 
				
			||||||
        decimal_places=6,
 | 
					        decimal_places=0,
 | 
				
			||||||
        verbose_name="X UTM",
 | 
					        verbose_name="X UTM",
 | 
				
			||||||
        null=True,
 | 
					        null=True,
 | 
				
			||||||
        blank=True
 | 
					        blank=True
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    utm_y = models.DecimalField(
 | 
					    utm_y = models.DecimalField(
 | 
				
			||||||
        max_digits=10,
 | 
					        max_digits=10,
 | 
				
			||||||
        decimal_places=6,
 | 
					        decimal_places=0,
 | 
				
			||||||
        verbose_name="Y UTM",
 | 
					        verbose_name="Y UTM",
 | 
				
			||||||
        null=True,
 | 
					        null=True,
 | 
				
			||||||
        blank=True
 | 
					        blank=True
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue