883 lines
38 KiB
HTML
883 lines
38 KiB
HTML
{% extends '_base.html' %}
|
|
{% load static %}
|
|
|
|
{% block sidebar %}
|
|
{% include 'sidebars/admin.html' %}
|
|
{% endblock sidebar %}
|
|
|
|
{% block navbar %}
|
|
{% include 'navbars/admin.html' %}
|
|
{% endblock navbar %}
|
|
|
|
|
|
{% block title %}درخواستها{% endblock %}
|
|
|
|
{% block style %}
|
|
<!-- DataTables CSS -->
|
|
<link rel="stylesheet" href="{% static 'assets/vendor/libs/datatables-bs5/datatables.bootstrap5.css' %}">
|
|
<link rel="stylesheet" href="{% static 'assets/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.css' %}">
|
|
<link rel="stylesheet" href="{% static 'assets/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.css' %}">
|
|
<!-- Persian Date Picker CSS -->
|
|
<link rel="stylesheet" href="https://unpkg.com/persian-datepicker@latest/dist/css/persian-datepicker.min.css">
|
|
|
|
{% endblock style %}
|
|
|
|
|
|
{% block content %}
|
|
{% include '_toasts.html' %}
|
|
|
|
<div class="container-xxl flex-grow-1 container-p-y">
|
|
|
|
<div class="row py-3 mb-4 card-header flex-column flex-md-row pb-0">
|
|
<div class="d-md-flex justify-content-between align-items-center dt-layout-start col-md-auto me-auto mt-0">
|
|
<h5 class="card-title mb-0 text-md-start text-center fw-bold">لیست درخواستها</h5>
|
|
</div>
|
|
<div class="d-md-flex justify-content-between align-items-center dt-layout-end col-md-auto ms-auto mt-0">
|
|
<div class="dt-buttons btn-group flex-wrap mb-0">
|
|
<div class="btn-group">
|
|
<button class="btn buttons-collection btn-label-primary dropdown-toggle me-4 d-none" type="button">
|
|
<span>
|
|
<span class="d-flex align-items-center gap-2">
|
|
<i class="icon-base bx bx-export me-sm-1"></i>
|
|
<span class="d-none d-sm-inline-block">خروجی</span>
|
|
</span>
|
|
</span>
|
|
</button>
|
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#requestModal">
|
|
<i class="bx bx-plus me-1"></i>
|
|
درخواست جدید
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-start justify-content-between">
|
|
<div class="content-left">
|
|
<span>کل درخواستها</span>
|
|
<div class="d-flex align-items-end mt-2">
|
|
<h4 class="mb-0 me-2">{{ total_count }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="avatar">
|
|
<span class="avatar-initial rounded bg-label-primary">
|
|
<i class="bx bx-list-ul bx-sm"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-start justify-content-between">
|
|
<div class="content-left">
|
|
<span>تکمیلشده</span>
|
|
<div class="d-flex align-items-end mt-2">
|
|
<h4 class="mb-0 me-2">{{ completed_count }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="avatar">
|
|
<span class="avatar-initial rounded bg-label-success">
|
|
<i class="bx bx-badge-check bx-sm"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-start justify-content-between">
|
|
<div class="content-left">
|
|
<span>در حال انجام</span>
|
|
<div class="d-flex align-items-end mt-2">
|
|
<h4 class="mb-0 me-2">{{ in_progress_count }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="avatar">
|
|
<span class="avatar-initial rounded bg-label-info">
|
|
<i class="bx bx-loader-circle bx-sm"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-xl-3">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-start justify-content-between">
|
|
<div class="content-left">
|
|
<span>در انتظار</span>
|
|
<div class="d-flex align-items-end mt-2">
|
|
<h4 class="mb-0 me-2">{{ pending_count }}</h4>
|
|
</div>
|
|
</div>
|
|
<div class="avatar">
|
|
<span class="avatar-initial rounded bg-label-warning">
|
|
<i class="bx bx-time bx-sm"></i>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-datatable table-responsive">
|
|
<table id="requests-table" class="datatables-basic table border-top">
|
|
<thead>
|
|
<tr>
|
|
<th>شناسه</th>
|
|
<th>فرآیند</th>
|
|
<th>مرحله فعلی</th>
|
|
<th>شماره اشتراک آب</th>
|
|
<th>نماینده</th>
|
|
<th>استان</th>
|
|
<th>امور</th>
|
|
<th>پیشرفت</th>
|
|
<th>وضعیت</th>
|
|
<th>تاریخ ایجاد</th>
|
|
<th>عملیات</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in instances_with_progress %}
|
|
<tr>
|
|
<td>{{ item.instance.code }}</td>
|
|
<td>{{ item.instance.process.name }}</td>
|
|
<td class="text-primary">
|
|
{% if item.instance.status == 'completed' %}
|
|
<a href="{% url 'processes:instance_summary' item.instance.id %}" class="text-primary">{{ item.instance.current_step.name|default:"--" }}</a>
|
|
{% elif item.instance.current_step %}
|
|
<a href="{% url 'processes:instance_steps' item.instance.id %}" class="text-primary">{{ item.instance.current_step.name }}</a>
|
|
{% else %}
|
|
--
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ item.instance.well.water_subscription_number }}</td>
|
|
<td>{% if item.instance.representative %}{{ item.instance.representative.get_full_name }}{% else %}-{% endif %}</td>
|
|
<td>{% if item.instance.well and item.instance.well.county %}{{ item.instance.well.county }}{% else %}-{% endif %}</td>
|
|
<td>{% if item.instance.well and item.instance.well.affairs %}{{ item.instance.well.affairs }}{% else %}-{% endif %}</td>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="progress me-2" style="width: 80px; height: 6px;">
|
|
<div class="progress-bar {% if item.progress_percentage == 100 %}bg-success{% elif item.progress_percentage >= 70 %}bg-info{% elif item.progress_percentage >= 40 %}bg-warning{% else %}bg-secondary{% endif %}" role="progressbar" style="width: {{ item.progress_percentage }}%;" aria-valuenow="{{ item.progress_percentage }}" aria-valuemin="0" aria-valuemax="100">
|
|
</div>
|
|
</div>
|
|
<small class="text-muted">{{ item.progress_percentage }}%</small>
|
|
</div>
|
|
</td>
|
|
<td>{{ item.instance.get_status_display_with_color|safe }}</td>
|
|
<td>{{ item.instance.jcreated }}</td>
|
|
<td>
|
|
<div class="d-inline-block">
|
|
<a href="javascript:;" class="btn btn-icon dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
|
|
<i class="icon-base bx bx-dots-vertical-rounded"></i>
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-end m-0">
|
|
<li>
|
|
{% if item.instance.status == 'completed' %}
|
|
<a href="{% url 'processes:instance_summary' item.instance.id %}" class="dropdown-item">
|
|
<i class="bx bx-show me-1"></i>مشاهده گزارش
|
|
</a>
|
|
{% else %}
|
|
<a href="{% url 'processes:instance_steps' item.instance.id %}" class="dropdown-item">
|
|
<i class="bx bx-show me-1"></i>مشاهده جزئیات
|
|
</a>
|
|
{% endif %}
|
|
</li>
|
|
<div class="dropdown-divider"></div>
|
|
<li>
|
|
<a href="#" class="dropdown-item text-danger" data-instance-id="{{ item.instance.id }}" data-instance-code="{{ item.instance.code }}" onclick="deleteRequest(this.getAttribute('data-instance-id'), this.getAttribute('data-instance-code'))">
|
|
<i class="bx bx-trash me-1"></i>حذف
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="11" class="text-center text-muted">موردی ثبت نشده است</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div class="modal fade" id="requestModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">درخواست جدید</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="requestForm">
|
|
{% csrf_token %}
|
|
<div class="row g-3">
|
|
<div class="col-sm-12">
|
|
<label class="form-label">فرآیند</label>
|
|
<select class="form-select" name="process" id="req_process" required>
|
|
{% for process in processes %}
|
|
<option value="{{ process.id }}">{{ process.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<hr class="mt-3 border border-dashed">
|
|
<div class="col-sm-12">
|
|
<label class="form-label">شماره اشتراک آب</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="req_water_sub" name="water_subscription_number" data-field="water_subscription_number" placeholder="مثال: 12345" required>
|
|
<button class="btn btn-outline-secondary" type="button" id="btnLookupWell">
|
|
بررسی/افزودن چاه
|
|
</button>
|
|
</div>
|
|
<div class="form-text" id="wellStatus"></div>
|
|
</div>
|
|
|
|
|
|
<!-- Well form fields (from WellForm) -->
|
|
<div id="wellFormBlock" class="col-sm-12" style="display:none;">
|
|
<div class="row g-3">
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_electricity_subscription_number">{{ well_form.electricity_subscription_number.label }}</label>
|
|
{{ well_form.electricity_subscription_number }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_water_meter_manufacturer">{{ well_form.water_meter_manufacturer.label }}</label>
|
|
<div class="input-group">
|
|
<select name="water_meter_manufacturer" class="form-select" id="id_water_meter_manufacturer">
|
|
<option value="" selected="">انتخاب شرکت سازنده</option>
|
|
{% for manufacturer in manufacturers %}
|
|
<option value="{{ manufacturer.id }}">{{ manufacturer.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<input type="text" class="form-control" id="id_new_manufacturer" name="new_manufacturer" placeholder="شرکت سازنده جدید" style="display:none;">
|
|
<button class="btn btn-outline-primary" type="button" id="btnToggleManufacturer">
|
|
<i class="bx bx-plus"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_water_meter_serial_number">{{ well_form.water_meter_serial_number.label }}</label>
|
|
{{ well_form.water_meter_serial_number }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_water_meter_old_serial_number">{{ well_form.water_meter_old_serial_number.label }}</label>
|
|
{{ well_form.water_meter_old_serial_number }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_utm_x">{{ well_form.utm_x.label }}</label>
|
|
{{ well_form.utm_x }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_utm_y">{{ well_form.utm_y.label }}</label>
|
|
{{ well_form.utm_y }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_utm_zone">{{ well_form.utm_zone.label }}</label>
|
|
{{ well_form.utm_zone }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_utm_hemisphere">{{ well_form.utm_hemisphere.label }}</label>
|
|
{{ well_form.utm_hemisphere }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_well_power">{{ well_form.well_power.label }}</label>
|
|
{{ well_form.well_power }}
|
|
</div>
|
|
<div class="col-sm-6"></div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_reference_letter_number">{{ well_form.reference_letter_number.label }}</label>
|
|
{{ well_form.reference_letter_number }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_reference_letter_date">{{ well_form.reference_letter_date.label }}</label>
|
|
<input type="text" class="form-control" id="id_reference_letter_date" name="reference_letter_date" placeholder="انتخاب تاریخ" readonly>
|
|
</div>
|
|
<div class="col-sm-12">
|
|
<label class="form-label" for="id_representative_letter_file">{{ well_form.representative_letter_file.label }}</label>
|
|
{{ well_form.representative_letter_file }}
|
|
<!-- نمایش فایل موجود -->
|
|
<div id="current-file-display" style="display: none; margin-top: 10px;">
|
|
<div class="alert alert-info d-flex align-items-center justify-content-between">
|
|
<div class="d-flex align-items-center">
|
|
<i class="bx bx-file me-2"></i>
|
|
<span id="current-file-name" class="text-truncate" style="max-width: 200px;" title=""></span>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-danger" onclick="removeCurrentFile()">
|
|
<i class="bx bx-trash me-1"></i>حذف
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<input type="hidden" id="remove-file" name="remove_file" value="false">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr class="mt-3 border border-dashed">
|
|
<div class="col-sm-12">
|
|
<label class="form-label">کد ملی نماینده</label>
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" id="rep_national_code" data-field="national_code" placeholder="مثال: 0012345678" maxlength="10" inputmode="numeric" pattern="\d*">
|
|
<button class="btn btn-outline-secondary" type="button" id="btnLookupRep">
|
|
بررسی/افزودن نماینده
|
|
</button>
|
|
</div>
|
|
<div class="form-text" id="repStatus"></div>
|
|
</div>
|
|
|
|
<div id="repNewFields" class="col-sm-12" style="display:none;">
|
|
<div class="row g-3">
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_first_name">{{ customer_form.first_name.label }}</label>
|
|
{{ customer_form.first_name }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_last_name">{{ customer_form.last_name.label }}</label>
|
|
{{ customer_form.last_name }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_phone_number_1">{{ customer_form.phone_number_1.label }}</label>
|
|
{{ customer_form.phone_number_1 }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_phone_number_2">{{ customer_form.phone_number_2.label }}</label>
|
|
{{ customer_form.phone_number_2 }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_national_code">{{ customer_form.national_code.label }}</label>
|
|
{{ customer_form.national_code }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_card_number">{{ customer_form.card_number.label }}</label>
|
|
{{ customer_form.card_number }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_account_number">{{ customer_form.account_number.label }}</label>
|
|
{{ customer_form.account_number }}
|
|
</div>
|
|
<div class="col-sm-6">
|
|
<label class="form-label" for="id_bank_name">{{ customer_form.bank_name.label }}</label>
|
|
{{ customer_form.bank_name }}
|
|
</div>
|
|
<div class="col-sm-12">
|
|
<label class="form-label" for="id_address">{{ customer_form.address.label }}</label>
|
|
{{ customer_form.address }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr class="mt-3 border border-dashed">
|
|
<div class="col-sm-12">
|
|
<label class="form-label">توضیحات</label>
|
|
<textarea class="form-control" rows="3" id="req_description" name="description"></textarea>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">بستن</button>
|
|
<button type="button" class="btn btn-primary" id="btnSaveRequest" disabled>ذخیره</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deleteConfirmModalLabel">تایید حذف</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p id="deleteConfirmText">آیا از حذف این درخواست اطمینان دارید؟</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">انصراف</button>
|
|
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">حذف</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block script %}
|
|
<!-- DataTables JS -->
|
|
<script src="{% static 'assets/vendor/libs/datatables-bs5/datatables-bootstrap5.js' %}"></script>
|
|
<!-- Persian DataTable defaults -->
|
|
<script src="{% static 'assets/js/persian-datatable.js' %}"></script>
|
|
|
|
<!-- Persian Date Picker JS -->
|
|
<script src="https://unpkg.com/persian-date@latest/dist/persian-date.min.js"></script>
|
|
<script src="https://unpkg.com/persian-datepicker@latest/dist/js/persian-datepicker.min.js"></script>
|
|
|
|
|
|
<script>
|
|
|
|
// Function to initialize Persian Date Picker
|
|
function initPersianDatePicker() {
|
|
if ($.fn.persianDatepicker && $('#id_reference_letter_date').length) {
|
|
try {
|
|
$('#id_reference_letter_date').persianDatepicker({
|
|
format: 'YYYY/MM/DD',
|
|
initialValue: false,
|
|
autoClose: true,
|
|
persianDigit: false,
|
|
observer: true,
|
|
calendar: {
|
|
persian: {
|
|
locale: 'fa',
|
|
leapYearMode: 'astronomical'
|
|
}
|
|
},
|
|
onSelect: function(unix) {
|
|
// تبدیل تاریخ شمسی به میلادی برای ارسال به سرور
|
|
const gregorianDate = new Date(unix);
|
|
const year = gregorianDate.getFullYear();
|
|
const month = String(gregorianDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(gregorianDate.getDate()).padStart(2, '0');
|
|
const gregorianDateString = `${year}-${month}-${day}`;
|
|
|
|
// نمایش تاریخ شمسی در فیلد
|
|
if (window.persianDate) {
|
|
const persianDate = new window.persianDate(unix);
|
|
const persianDateString = persianDate.format('YYYY/MM/DD');
|
|
$('#id_reference_letter_date').val(persianDateString);
|
|
} else {
|
|
// اگر persianDate در دسترس نبود، تاریخ میلادی را نمایش بده
|
|
$('#id_reference_letter_date').val(gregorianDateString);
|
|
}
|
|
|
|
// ذخیره تاریخ میلادی در فیلد مخفی برای ارسال به سرور
|
|
$('#id_reference_letter_date').attr('data-gregorian', gregorianDateString);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.error('Error initializing Persian Date Picker:', e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
$(function() {
|
|
// Initialize DataTable similar to customer_list
|
|
$('#requests-table').DataTable({
|
|
pageLength: 10,
|
|
lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "همه"]],
|
|
order: [[0, 'desc']],
|
|
responsive: true,
|
|
});
|
|
let currentWellId = null;
|
|
let currentRepId = null;
|
|
let wellChecked = false;
|
|
let repChecked = false;
|
|
|
|
function setStatus(el, text, type) {
|
|
$(el).text(text).removeClass('text-danger text-success text-muted').addClass(type ? 'text-' + type : 'text-muted');
|
|
}
|
|
|
|
function checkSaveButton() {
|
|
const canSave = wellChecked && repChecked;
|
|
$('#btnSaveRequest').prop('disabled', !canSave);
|
|
}
|
|
|
|
// Inline error helpers
|
|
function clearInlineErrors() {
|
|
$('#requestModal .is-invalid').removeClass('is-invalid');
|
|
$('#requestModal .invalid-feedback.inline-error').remove();
|
|
}
|
|
|
|
function applyErrorTo(selector, message) {
|
|
const $el = $(selector);
|
|
if (!$el.length) return false;
|
|
$el.addClass('is-invalid');
|
|
const $feedback = $('<div class="invalid-feedback inline-error"></div>').text(message);
|
|
const $grp = $el.closest('.input-group, .form-group, .mb-3');
|
|
if ($grp.length) {
|
|
$feedback.insertAfter($grp);
|
|
} else {
|
|
$feedback.insertAfter($el);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Generic field resolution with small exception map
|
|
const exceptionMap = {
|
|
water_subscription_number: '#req_water_sub',
|
|
national_code: '#rep_national_code',
|
|
representative: '#rep_national_code'
|
|
};
|
|
|
|
function findFieldSelector(field, context) {
|
|
const $ctx = context ? $(context) : $('#requestModal');
|
|
let $el = $ctx.find(`#id_${field}`).first();
|
|
if ($el.length) return $el;
|
|
$el = $ctx.find(`[name="${field}"]`).first();
|
|
if ($el.length) return $el;
|
|
$el = $ctx.find(`[data-field="${field}"]`).first();
|
|
if ($el.length) return $el;
|
|
const ex = exceptionMap[field];
|
|
return ex ? $(ex) : $();
|
|
}
|
|
|
|
function showInlineErrors(errors) {
|
|
if (!errors) return;
|
|
let nonFieldWell = '';
|
|
let nonFieldCustomer = '';
|
|
// Request-level errors (e.g., process)
|
|
if (errors.request) {
|
|
for (const key in errors.request) {
|
|
const msgs = Array.isArray(errors.request[key]) ? errors.request[key] : [errors.request[key]];
|
|
if (key === '__all__' || key === 'non_field_errors') { continue; }
|
|
applyErrorTo(findFieldSelector(key, '#requestForm'), msgs[0]);
|
|
}
|
|
}
|
|
if (errors.well) {
|
|
for (const key in errors.well) {
|
|
const msgs = Array.isArray(errors.well[key]) ? errors.well[key] : [errors.well[key]];
|
|
if (key === '__all__' || key === 'non_field_errors') { nonFieldWell = msgs.join('، '); continue; }
|
|
applyErrorTo(findFieldSelector(key, '#wellFormBlock'), msgs[0]);
|
|
}
|
|
}
|
|
if (errors.customer) {
|
|
for (const key in errors.customer) {
|
|
const msgs = Array.isArray(errors.customer[key]) ? errors.customer[key] : [errors.customer[key]];
|
|
if (key === '__all__' || key === 'non_field_errors') { nonFieldCustomer = msgs.join('، '); continue; }
|
|
applyErrorTo(findFieldSelector(key, '#repNewFields'), msgs[0]);
|
|
}
|
|
}
|
|
if (nonFieldWell) setStatus('#wellStatus', nonFieldWell, 'danger');
|
|
if (nonFieldCustomer) setStatus('#repStatus', nonFieldCustomer, 'danger');
|
|
}
|
|
|
|
$('#btnLookupWell').on('click', function() {
|
|
const sub = $('#req_water_sub').val().trim();
|
|
if (!sub) { setStatus('#wellStatus', 'لطفا شماره اشتراک آب را وارد کنید', 'danger'); return; }
|
|
setStatus('#wellStatus', 'در حال بررسی...', 'muted');
|
|
wellChecked = true;
|
|
checkSaveButton();
|
|
$.get('{% url "processes:lookup_well_by_subscription" %}', { water_subscription_number: sub })
|
|
.done(function(resp){
|
|
if (resp.exists) {
|
|
currentWellId = resp.well.id;
|
|
$('#wellFormBlock').show();
|
|
// Initialize Persian Date Picker after well form is shown
|
|
setTimeout(initPersianDatePicker, 100);
|
|
|
|
// Prefill well form
|
|
$('#id_electricity_subscription_number').val(resp.well.electricity_subscription_number || '');
|
|
$('#id_water_meter_serial_number').val(resp.well.water_meter_serial_number || '');
|
|
$('#id_water_meter_old_serial_number').val(resp.well.water_meter_old_serial_number || '');
|
|
$('#id_water_meter_manufacturer').val(resp.well.water_meter_manufacturer || '');
|
|
$('#id_utm_x').val(resp.well.utm_x || '');
|
|
$('#id_utm_y').val(resp.well.utm_y || '');
|
|
$('#id_utm_zone').val(resp.well.utm_zone || '');
|
|
$('#id_utm_hemisphere').val(resp.well.utm_hemisphere || '');
|
|
$('#id_well_power').val(resp.well.well_power || '');
|
|
$('#id_reference_letter_number').val(resp.well.reference_letter_number || '');
|
|
// Prefill date: show Persian in input, keep Gregorian in data attribute
|
|
if (resp.well.reference_letter_date) {
|
|
try {
|
|
if (window.persianDate) {
|
|
const gregorianDate = new Date(resp.well.reference_letter_date);
|
|
const persianDateObj = new window.persianDate(gregorianDate);
|
|
const persianDateString = persianDateObj.format('YYYY/MM/DD');
|
|
$('#id_reference_letter_date').val(persianDateString);
|
|
} else {
|
|
$('#id_reference_letter_date').val(resp.well.reference_letter_date);
|
|
}
|
|
$('#id_reference_letter_date').attr('data-gregorian', resp.well.reference_letter_date);
|
|
} catch (e) {
|
|
$('#id_reference_letter_date').val(resp.well.reference_letter_date);
|
|
}
|
|
} else {
|
|
$('#id_reference_letter_date').val('');
|
|
$('#id_reference_letter_date').removeAttr('data-gregorian');
|
|
}
|
|
// Existing representative letter file display
|
|
if (resp.well.representative_letter_file_url) {
|
|
$('#current-file-display').show();
|
|
const fileName = resp.well.representative_letter_file_name || 'فایل موجود';
|
|
$('#current-file-name').text(fileName).attr('title', fileName);
|
|
$('#id_representative_letter_file').hide();
|
|
$('#remove-file').val('false');
|
|
} else {
|
|
$('#current-file-display').hide();
|
|
$('#id_representative_letter_file').show();
|
|
$('#remove-file').val('false');
|
|
}
|
|
setStatus('#wellStatus', 'چاه یافت شد', 'success');
|
|
} else {
|
|
currentWellId = null;
|
|
$('#wellFormBlock').show();
|
|
$('#wellFormBlock').find('input, select').val('');
|
|
$('#id_reference_letter_date').removeAttr('data-gregorian');
|
|
// Reset file UI for new well
|
|
$('#current-file-display').hide();
|
|
$('#id_representative_letter_file').show().val('');
|
|
$('#remove-file').val('false');
|
|
// Initialize Persian Date Picker after well form is shown
|
|
setTimeout(initPersianDatePicker, 100);
|
|
setStatus('#wellStatus', 'چاه یافت نشد. اطلاعات چاه را وارد کنید.', 'danger');
|
|
}
|
|
})
|
|
.fail(function(){ setStatus('#wellStatus', 'خطا در بررسی چاه', 'danger'); });
|
|
});
|
|
|
|
$('#btnLookupRep').on('click', function() {
|
|
const nc = $('#rep_national_code').val().trim();
|
|
if (!nc) { setStatus('#repStatus', 'لطفا کد ملی نماینده را وارد کنید', 'danger'); return; }
|
|
setStatus('#repStatus', 'در حال بررسی...', 'muted');
|
|
repChecked = true;
|
|
checkSaveButton();
|
|
$.get('{% url "processes:lookup_representative_by_national_code" %}', { national_code: nc })
|
|
.done(function(resp){
|
|
if (resp.exists) {
|
|
currentRepId = resp.user.id;
|
|
$('#repNewFields').show();
|
|
// Prefill customer form fields for editing
|
|
$('#id_first_name').val(resp.user.first_name || '');
|
|
$('#id_last_name').val(resp.user.last_name || '');
|
|
if (resp.user.profile) {
|
|
$('#id_national_code').val(resp.user.profile.national_code || nc);
|
|
$('#id_phone_number_1').val(resp.user.profile.phone_number_1 || '');
|
|
$('#id_phone_number_2').val(resp.user.profile.phone_number_2 || '');
|
|
$('#id_card_number').val(resp.user.profile.card_number || '');
|
|
$('#id_account_number').val(resp.user.profile.account_number || '');
|
|
$('#id_bank_name').val(resp.user.profile.bank_name || '');
|
|
$('#id_address').val(resp.user.profile.address || '');
|
|
} else {
|
|
$('#id_national_code').val(nc);
|
|
$('#id_phone_number_1').val('');
|
|
$('#id_phone_number_2').val('');
|
|
$('#id_card_number').val('');
|
|
$('#id_account_number').val('');
|
|
$('#id_bank_name').val('');
|
|
$('#id_address').val('');
|
|
}
|
|
setStatus('#repStatus', 'نماینده یافت شد.', 'success');
|
|
} else {
|
|
currentRepId = null;
|
|
$('#repNewFields').show();
|
|
// Clear form and prefill national code
|
|
$('#id_first_name').val('');
|
|
$('#id_last_name').val('');
|
|
$('#id_national_code').val(nc);
|
|
$('#id_phone_number_1').val('');
|
|
$('#id_phone_number_2').val('');
|
|
$('#id_card_number').val('');
|
|
$('#id_account_number').val('');
|
|
$('#id_bank_name').val('');
|
|
$('#id_address').val('');
|
|
setStatus('#repStatus', 'نماینده یافت نشد. لطفا اطلاعات را تکمیل کنید.', 'danger');
|
|
}
|
|
})
|
|
.fail(function(){ setStatus('#repStatus', 'خطا در بررسی نماینده', 'danger'); });
|
|
});
|
|
|
|
$('#btnSaveRequest').on('click', function(){
|
|
clearInlineErrors();
|
|
// Use form's native FormData - much cleaner!
|
|
const formData = new FormData(document.getElementById('requestForm'));
|
|
|
|
// Add custom fields that aren't in the form
|
|
if (currentWellId) formData.append('well_id', currentWellId);
|
|
if (currentRepId) formData.append('representative_id', currentRepId);
|
|
|
|
// Handle special national_code logic (prefer visible field)
|
|
const ncField = $('#id_national_code').val();
|
|
if (ncField) {
|
|
formData.set('national_code', ncField);
|
|
} else {
|
|
formData.set('national_code', $('#rep_national_code').val().trim());
|
|
}
|
|
|
|
// Handle Persian date conversion
|
|
const gregorianDate = $('#id_reference_letter_date').attr('data-gregorian');
|
|
if (gregorianDate) {
|
|
formData.set('reference_letter_date', gregorianDate);
|
|
}
|
|
|
|
const $btn = $(this).prop('disabled', true).text('در حال ذخیره...');
|
|
$.ajax({
|
|
url: '{% url "processes:create_request_with_entities" %}',
|
|
method: 'POST',
|
|
data: formData,
|
|
processData: false,
|
|
contentType: false,
|
|
}).done(function(resp){
|
|
if (resp.ok) {
|
|
showToast('درخواست با موفقیت ثبت شد', 'success');
|
|
if (resp.redirect) {
|
|
setTimeout(function(){ window.location.href = resp.redirect; }, 800);
|
|
} else {
|
|
setTimeout(function(){ location.reload(); }, 1200);
|
|
}
|
|
} else {
|
|
clearInlineErrors();
|
|
if (resp.errors) {
|
|
showInlineErrors(resp.errors);
|
|
}
|
|
const msg = buildErrorMessage(resp);
|
|
showToast(msg, 'danger');
|
|
}
|
|
}).fail(function(xhr){
|
|
let msg = 'خطا در ذخیره';
|
|
try {
|
|
const resp = JSON.parse(xhr.responseText);
|
|
clearInlineErrors();
|
|
if (resp && resp.errors) {
|
|
showInlineErrors(resp.errors);
|
|
}
|
|
msg = buildErrorMessage(resp) || msg;
|
|
} catch(e) {}
|
|
showToast(msg, 'danger');
|
|
}).always(function(){
|
|
$btn.prop('disabled', false).text('ذخیره');
|
|
});
|
|
});
|
|
|
|
function buildErrorMessage(resp){
|
|
if (!resp) return '';
|
|
if (resp.error) return resp.error;
|
|
if (resp.errors) {
|
|
// Collect form-related errors
|
|
const parts = [];
|
|
if (resp.errors.customer) {
|
|
parts.push('خطای نماینده: ' + flattenErrors(resp.errors.customer));
|
|
}
|
|
if (resp.errors.well) {
|
|
parts.push('خطای چاه: ' + flattenErrors(resp.errors.well));
|
|
}
|
|
return parts.join(' | ');
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function flattenErrors(errorsObj){
|
|
if (typeof errorsObj === 'string') return errorsObj;
|
|
try {
|
|
const parts = [];
|
|
for (const k in errorsObj){
|
|
const v = errorsObj[k];
|
|
if (Array.isArray(v)) parts.push(`${k}: ${v[0]}`);
|
|
else if (typeof v === 'string') parts.push(`${k}: ${v}`);
|
|
}
|
|
return parts.join('، ');
|
|
} catch(e){
|
|
return '';
|
|
}
|
|
}
|
|
|
|
$('#btnToggleManufacturer').on('click', function() {
|
|
const $select = $('#id_water_meter_manufacturer');
|
|
const $input = $('#id_new_manufacturer');
|
|
const $btn = $(this);
|
|
|
|
if ($select.is(':visible')) {
|
|
$select.hide();
|
|
$input.show().focus();
|
|
$btn.html('<i class="bx bx-check"></i>');
|
|
} else {
|
|
$input.hide();
|
|
$select.show();
|
|
$btn.html('<i class="bx bx-plus"></i>');
|
|
}
|
|
});
|
|
|
|
// Enforce digit-only and max length for national code input
|
|
$('#rep_national_code').on('input', function() {
|
|
const cleaned = (this.value || '').replace(/\D/g, '').slice(0, 10);
|
|
if (this.value !== cleaned) {
|
|
this.value = cleaned;
|
|
}
|
|
});
|
|
|
|
$('#requestModal').on('hidden.bs.modal', function(){
|
|
$('#requestForm')[0].reset();
|
|
$('#wellFormBlock').hide();
|
|
$('#repNewFields').hide();
|
|
$('#id_reference_letter_date').removeAttr('data-gregorian');
|
|
// Reset file UI
|
|
$('#current-file-display').hide();
|
|
$('#id_representative_letter_file').show().val('');
|
|
$('#remove-file').val('false');
|
|
setStatus('#wellStatus', '', '');
|
|
setStatus('#repStatus', '', '');
|
|
currentWellId = null;
|
|
currentRepId = null;
|
|
wellChecked = false;
|
|
repChecked = false;
|
|
checkSaveButton();
|
|
clearInlineErrors(); // Clear inline errors on modal close
|
|
});
|
|
|
|
// Handle selecting a new file: hide existing file display and cancel removal flag
|
|
$('#id_representative_letter_file').on('change', function() {
|
|
if (this.files && this.files.length > 0) {
|
|
$('#current-file-display').hide();
|
|
$('#remove-file').val('false');
|
|
}
|
|
});
|
|
|
|
// Expose remove function
|
|
window.removeCurrentFile = function() {
|
|
$('#current-file-display').hide();
|
|
$('#remove-file').val('true');
|
|
$('#id_representative_letter_file').show().val('');
|
|
};
|
|
|
|
// Delete request function
|
|
window.deleteRequest = function(instanceId, instanceCode) {
|
|
// Set modal content
|
|
document.getElementById('deleteConfirmText').textContent = `آیا از حذف درخواست ${instanceCode} اطمینان دارید؟`;
|
|
|
|
// Show modal
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteConfirmModal'));
|
|
modal.show();
|
|
|
|
// Handle confirm button click
|
|
document.getElementById('confirmDeleteBtn').onclick = function() {
|
|
$.ajax({
|
|
url: '{% url "processes:delete_request" 0 %}'.replace('0', instanceId),
|
|
type: 'POST',
|
|
data: {
|
|
'csrfmiddlewaretoken': $('[name=csrfmiddlewaretoken]').val()
|
|
},
|
|
success: function(response) {
|
|
if (response.success) {
|
|
showToast(response.message, 'success');
|
|
modal.hide();
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 1500);
|
|
} else {
|
|
showToast(response.message, 'danger');
|
|
}
|
|
},
|
|
error: function() {
|
|
showToast('خطا در ارتباط با سرور', 'danger');
|
|
}
|
|
});
|
|
};
|
|
};
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|
|
|