fix nazar sanji and eghdamha

This commit is contained in:
javad committed 2026-07-24 22:11:52 +03:30
1 parent b86ccb373b
commit 6a6616f24a
8 files changed
+542 -91

No files matched your search

@@ -0,0 +1,18 @@
# Generated by Django 3.2.25 on 2026-07-24 18:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('admin_panel', '0054_auto_20260715_2250'),
]
operations = [
migrations.AddField(
model_name='action',
name='nextNazarDT',
field=models.DateTimeField(blank=True, default=None, null=True),
),
]
+1
View File
@@ -461,6 +461,7 @@ class Action(models.Model):
result = models.CharField(max_length=500, null=True, blank=True)
status = models.CharField(max_length=50, null=True, blank=True)
nazar = models.CharField(max_length=50, null=True, blank=True, default=None)
nextNazarDT = models.DateTimeField(null=True, blank=True, default=None)
nazarDT = models.DateTimeField(null=True, blank=True, default=None)
grade = models.IntegerField(null=True, blank=True, default=None)
@@ -102,6 +102,11 @@
<a href="{% url 'admin_panel:financial_dashboard' %}" style="padding-right: 10px;" class="navbar-item">
داشبورد مدیریتی
</a>
<a href="{% url 'admin_panel:rezayat_dashboard' %}" style="padding-right: 10px;" class="navbar-item">
گزارش نظر سنجی ها
</a>
</div>
</div>
@@ -5,8 +5,62 @@
{% endwith %}
{% if customer %}
<div class="box mb-5 has-background-white-ter" style="border: 1px solid #e0e0e0; box-shadow: none; border-radius: 6px;">
<form method="GET" action="{% url 'admin_panel:customerAction' customer.id %}">
<div class="columns is-mobile is-multiline is-vcentered m-0">
<div class="column is-4-desktop is-12-mobile pb-0">
<div class="field">
<label class="label is-small">نوع اقدام</label>
<div class="control">
<div class="select is-fullwidth is-small">
<select name="actionType">
<option value="all" {% if filter_type == 'all' %}selected{% endif %}>همه موارد</option>
<option value="bazdid" {% if filter_type == 'bazdid' %}selected{% endif %}>بازدید</option>
<option value="nasb" {% if filter_type == 'nasb' %}selected{% endif %}>نصب</option>
<option value="vosol" {% if filter_type == 'vosol' %}selected{% endif %}>وصول مطالبات</option>
<option value="hamahangi" {% if filter_type == 'hamahangi' %}selected{% endif %}>هماهنگی</option>
<option value="inCall" {% if filter_type == 'inCall' %}selected{% endif %}>تماس ورودی</option>
<option value="outCall" {% if filter_type == 'outCall' %}selected{% endif %}>تماس خروجی</option>
</select>
</div>
</div>
</div>
</div>
<div class="column is-4-desktop is-12-mobile pb-0">
<div class="field">
<label class="label is-small">وضعیت انجام</label>
<div class="control">
<div class="select is-fullwidth is-small">
<select name="status">
<option value="all" {% if filter_status == 'all' %}selected{% endif %}>همه وضعیت‌ها</option>
<option value="waite" {% if filter_status == 'waite' %}selected{% endif %}>در انتظار (باز)</option>
<option value="finish" {% if filter_status == 'finish' %}selected{% endif %}>انجام شده</option>
<option value="cancell" {% if filter_status == 'cancell' %}selected{% endif %}>لغو شده</option>
</select>
</div>
</div>
</div>
</div>
<div class="column is-4-desktop is-12-mobile mt-4 has-text-left pb-0">
<button type="submit" class="button is-info is-small px-4">اعمال فیلتر</button>
<a href="{% url 'admin_panel:customerAction' customer.id %}" class="button is-light is-small">حذف فیلتر</a>
</div>
</div>
</form>
</div>
<span id="showActionBox" class="button is-info mb-5">ایجاد وظیفه جدید</span>
{% endif %}
{% if not actions and not actionPr %}
<div class="notification is-warning is-light has-text-centered">
هیچ اقدامی با مشخصات انتخاب‌شده یافت نشد!
</div>
{% endif %}
{% for action in actions %}
<div customer_id='{{ action.customer.id }}' action_id='{{ action.id }}' class="columns is-mobile is-multiline is-gapless actionBox {{ bgBox }}">
<div class="column is-12-mobile is-12-desktop actionBoxDesc actionBoxOpenB">
@@ -35,34 +89,28 @@
</div>
<div class="column is-4-mobile is-4-desktop">
<div class="has-text-centered p-3">
<div class="has-text-centered p-3">
<span class="ss02">{{ action.actionDateTime | toJalali }}</span>
</div>
</div>
<div class="column is-8-mobile is-4-desktop">
<div class="has-text-centered actionBoxFtBox p-3">
<div class="has-text-centered actionBoxFtBox p-3">
<a href="{% url 'admin_panel:customerNew' action.customer.id %}">
<span>{{ action.customer.name_family }}</span>
</a>
</div>
</div>
<div class="column is-12-mobile is-4-desktop is-hidden-mobile">
<div class="has-text-centered p-3">
<div class="has-text-centered p-3">
<span>{{ action.forUser.first_name }} {{ action.forUser.last_name }}</span>
</div>
</div>
</div>
{% endfor %}
{% if actionPr %}
<hr>
<p style="
text-align: center;
margin-bottom: 20px;
color: #d90000;
font-size: 18px;
"> کارهای مانده از روزهای قبل</p>
<p style="text-align: center; margin-bottom: 20px; color: #d90000; font-size: 18px;"> کارهای مانده از روزهای قبل</p>
{% for action in actionPr %}
<div customer_id='{{ action.customer.id }}' action_id='{{ action.id }}' class="columns is-mobile is-multiline is-gapless actionBox {{ bgBox }}">
@@ -89,26 +137,23 @@
<span class="is-result">نتیجه</span>
<span>{% if action.result %} {{ action.result }} {% else %}هنوز گزارش یا نتیجه ای ثبت نشده!{% endif %}</span>
</div>
</div>
<div class="column is-4-mobile is-4-desktop">
<div class="has-text-centered p-3">
<div class="has-text-centered p-3">
<span class="ss02">{{ action.actionDateTime | toJalali }}</span>
</div>
</div>
<div class="column is-8-mobile is-4-desktop">
<div class="has-text-centered actionBoxFtBox p-3">
<div class="has-text-centered actionBoxFtBox p-3">
<a href="{% url 'admin_panel:customerNew' action.customer.id %}">
<span>{{ action.customer.name_family }}</span>
</a>
</div>
</div>
<div class="column is-12-mobile is-4-desktop is-hidden-mobile">
<div class="has-text-centered p-3">
<div class="has-text-centered p-3">
<span>{{ action.forUser.first_name }} {{ action.forUser.last_name }}</span>
</div>
</div>
</div>
{% endfor %}
@@ -127,7 +172,7 @@
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title is-hidden">ایجاد وظیفه</p>
<button class="delete closeActionBox" aria-label="close"></button>
<button type="button" class="delete closeActionBox" aria-label="close"></button>
</header>
<section class="modal-card-body">
@@ -135,16 +180,17 @@
<div class="field column is-6-desktop is-12-mobile m-0">
<label class="label">نوع اقدام</label>
<div class="control">
<div class="select" style="width: 100%;">
<select id="actionType" required name="actionType" style="width: 100%;">
<option>انتخاب کنید</option>
<option value="">انتخاب کنید</option>
<option value="bazdid">بازدید</option>
<option value="nasb">نصب</option>
<option value="vosol">وصول مطالبات</option>
<option value="hamahangi">هماهنگی</option>
<option value="inCall">تماس ورودی</option>
<option value="outCall">تماس خروجی</option>
</select>
</div>
</div>
</div>
@@ -159,20 +205,17 @@
<div class="field column is-12-desktop is-12-mobile m-0">
<div class="control">
<label class="label mb-0">توضیحات برای یادآوری حین انجام کار:</label>
<p class="is-size-7 ">راجب کاری که در آینده میخواهید انجام دهید توضیحاتی را بنویسید</p>
<textarea id="desc" required name="desc" class="textarea " placeholder="مثلا: بامشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea>
<p class="is-size-7">راجب کاری که در آینده میخواهید انجام دهید توضیحاتی را بنویسید</p>
<textarea id="desc" required name="desc" class="textarea" placeholder="مثلا: بامشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea>
</div>
</div>
<div class="field column is-6-desktop is-12-mobile m-0">
<label class="label">توسط</label>
<div class="control">
<div class="select" style="width: 100%;">
<select id="userID" required name="userID" style="width: 100%;">
<option>انتخاب کنید</option>
<!-- perms.admin_panel.ViewStore !-->
<option value="">انتخاب کنید</option>
{% if perms.admin_panel.ViewStore %}
{% for auser in allUsers %}
<option value="{{ auser.id }}">{{ auser.first_name }} {{ auser.last_name }}</option>
@@ -183,18 +226,15 @@
{% endif %}
</select>
</div>
</div>
</div>
<div class="field column is-12-desktop is-12-mobile m-0">
<div class="control">
<label class="checkbox">
<input name="is_done" id="checkIsDone" type="checkbox">
این کار انجام شده
</label>
</div>
</div>
@@ -209,25 +249,20 @@
<div class="field column is-12-desktop is-12-mobile m-0 showWhenDone is-hidden">
<div class="control">
<label class="label mb-0">نتیجه</label>
<p class="is-size-7 " >نتیجه کار یا پیگیری که انجام داده اید را بنویسید</p>
<p class="is-size-7">نتیجه کار یا پیگیری که انجام داده اید را بنویسید</p>
<textarea name="result" id="actionResult" class="textarea" placeholder="نتیجه فعالیت انجام شده را بنویسید"></textarea>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<span class="button">حذف</span>
<span class="button">لغو</span>
<!--
<button class="button closeActionBox">بستن</button>!-->
<button type="button" class="button closeActionBox">لغو</button>
<input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/>
</footer>
</div>
</form>
<script>
$(".closeActionBox").click(function(){
$("#ActionBox").removeClass("is-active");
@@ -236,7 +271,7 @@
$("#showActionBox").click(function(){
$("#ActionBox").addClass("is-active");
{% if customer %}
var NewActionUrl ="{% url 'admin_panel:customerNewAction' customer.id %}";
var NewActionUrl = "{% url 'admin_panel:customerNewAction' customer.id %}";
$("#ActionBox").prop("action", NewActionUrl);
{% endif %}
$("#actionType").val('');
@@ -253,7 +288,7 @@
$("#checkIsDone").change(function() {
if(this.checked) {
$(".showWhenDone").removeClass("is-hidden");
}else{
} else {
$(".showWhenDone").addClass("is-hidden");
}
});
@@ -266,9 +301,9 @@
actionEditUrl = actionEditUrl.replace('1111', customer_id);
actionEditUrl = actionEditUrl.replace('2222', action_id);
$("#ActionBox").prop("action", actionEditUrl);
$.getJSON('/panel/ActionJson/'+action_id, function(data) {
$.getJSON('/panel/ActionJson/' + action_id, function(data) {
ActionParseData = JSON.parse(data);
console.log(ActionParseData)
console.log(ActionParseData);
var actionDateTime0 = new Date(ActionParseData[0]['fields']['actionDateTime']);
var finishDateTime = null;
if(ActionParseData[0]['fields']['finishDateTime'] != null){
@@ -286,7 +321,7 @@
if(ActionParseData[0]['fields']['status'] == 'finish'){
$("#checkIsDone").prop('checked', true);
$(".showWhenDone").removeClass("is-hidden");
}else{
} else {
$("#checkIsDone").prop('checked', false);
$(".showWhenDone").addClass("is-hidden");
}
@@ -328,8 +363,5 @@
enabled: false,
}
},
})
//.setDate(1382976091100);
});
</script>
@@ -61,30 +61,28 @@
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title is-hidden">ایجاد وظیفه</p>
<button class="delete closeActionBox" aria-label="close"></button>
<button type="button" class="delete closeActionBox" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="columns is-mobile is-multiline">
<div class="field column is-12-desktop is-12-mobile m-0">
<div class="control" id="customerData">
<p id="csName" class="has-text-centered is-size-4 ">محمد جواد فصاحت</p>
<p class="has-text-centered is-size-5 csphones">
<p id="csName" class="has-text-centered is-size-4">محمد جواد فصاحت</p>
<p class="has-text-centered is-size-5 csphones">
<span id="csphone1" class="is-hidden hidephone"></span>
<span id="csphone2" class="is-hidden hidephone"></span>
<span id="csphone3" class="is-hidden hidephone"></span></p>
<span id="csphone3" class="is-hidden hidephone"></span>
</p>
</div>
</div>
<div class="field column is-12-desktop is-12-mobile m-0">
<div class="control">
<label class="label mb-0">نکات و نظر مشتری:</label>
<p class="is-size-7 "> </p>
<p class="is-size-7"> </p>
<textarea id="csDesc" required name="csDesc" class="textarea " placeholder="مثلا: بامشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea>
</div>
</div>
@@ -119,23 +117,19 @@
<option value="15">متوسط</option>
<option value="10">ضعیف</option>
<option value="5">بد</option>
<option value="-1">عدم موفقیت در انجام نظرسنجی (تعویق ۳ روز)</option>
<option value="-2">کنسل کردن این نظرسنجی</option>
</select>
</div>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<span class="button">حذف</span>
<span class="button">لغو</span>
<!--
<button class="button closeActionBox">بستن</button>!-->
<span class="button closeActionBox">لغو</span>
<input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/>
</footer>
</div>
</form>
@@ -173,14 +167,11 @@
$(".hidephone").addClass("is-hidden");
$("#csDesc").val("")
$("#csGrade").val("")
//$("#sabtBtn05").val("ویرایش");
var actionEditUrl = "{% url 'admin_panel:rezayatEdit' 2222 %}";
var action_id = $(this).parent().attr("action_id");
var customer_id = $(this).parent().attr("customer_id");
//actionEditUrl = actionEditUrl.replace('1111', customer_id);
actionEditUrl = actionEditUrl.replace('2222', action_id);
$("#csphone1")
$.getJSON('/panel/CustomerJson/'+customer_id, function(data) {
CustomerParseData = JSON.parse(data);
$("#csName").html(CustomerParseData[0]['fields']['name_family']);
@@ -204,12 +195,7 @@
$("#ActionBox").prop("action", actionEditUrl);
$.getJSON('/panel/ActionJson/'+action_id, function(data) {
ActionParseData = JSON.parse(data);
//console.log(ActionParseData)
$("#ActionBox").addClass("is-active");
//$("#actionType").val(ActionParseData[0]['fields']['actionType']);
//$("#desc").val(ActionParseData[0]['fields']['desc']);
//$("#userID").val(ActionParseData[0]['fields']['forUser']);
//$("#actionResult").val(ActionParseData[0]['fields']['result']);
$("#csDesc").val(ActionParseData[0]['fields']['nazar'])
$("#csGrade").val(ActionParseData[0]['fields']['grade'])
});
@@ -250,7 +236,5 @@
}
},
})
//.setDate(1382976091100);
</script>
@@ -0,0 +1,290 @@
{% extends "./base.html" %}
{% load custom_tags %}
{% load static %}
{% block bodyPage %}
<div class="container is-fluid mt-4 mb-6">
<div class="columns is-vcentered pb-4 mb-5" style="border-bottom: 2px solid #f5f5f5;">
<div class="column is-12 has-text-centered-mobile">
<h1 class="title is-4 is-size-5-mobile has-text-dark">داشبورد کنترل کیفیت و رضایت مشتریان</h1>
<p class="subtitle is-7 has-text-grey mt-1">مانیتورینگ بازخوردها و پیگیری مشتریان ناراضی جهت جلب رضایت مجدد</p>
</div>
</div>
<div class="columns is-multiline">
<div class="column is-12-mobile is-4-tablet">
<div class="box p-4 has-background-white shadow-card border-left-success h-100">
<div class="level is-mobile m-0">
<div class="level-left">
<div>
<p class="heading has-text-grey-dark is-size-7 mb-1">کل نظرسنجی‌های انجام شده</p>
<p class="title is-size-4 is-size-5-mobile has-text-success-dark">{{ total_surveys }} <span class="is-size-6">مورد</span></p>
</div>
</div>
</div>
</div>
</div>
<div class="column is-12-mobile is-4-tablet">
<div class="box p-4 has-background-white shadow-card border-left-warning h-100">
<div class="level is-mobile m-0">
<div class="level-left">
<div>
<p class="heading has-text-grey-dark is-size-7 mb-1">میانگین امتیاز سیستم</p>
<p class="title is-size-4 is-size-5-mobile has-text-warning-dark">{{ avg_grade }} <span class="is-size-6">از ۲۰</span></p>
</div>
</div>
</div>
</div>
</div>
<div class="column is-12-mobile is-4-tablet">
<div class="box p-4 has-background-white shadow-card border-left-danger h-100">
<div class="level is-mobile m-0">
<div class="level-left">
<div>
<p class="heading has-text-grey-dark is-size-7 mb-1">مشتریان ناراضی (نیازمند پیگیری)</p>
<p class="title is-size-4 is-size-5-mobile has-text-danger-dark">{{ unhappy_count }} <span class="is-size-6">نفر</span></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="columns is-multiline mt-1">
<div class="column is-12-mobile is-4-desktop">
<div class="box p-4 shadow-card h-100">
<p class="title is-6 is-size-7-mobile pb-2 mb-4" style="border-bottom: 1px solid #f0f0f0;">
📊 توزیع سطوح رضایت‌مندی
</p>
{% if has_chart_data %}
<div class="chart-wrapper-medium">
<canvas id="satisfactionChart"></canvas>
</div>
{% else %}
<div class="notification is-light has-text-centered my-5 is-size-7">
هنوز داده‌ای برای رسم نمودار رضایت‌مندی ثبت نشده است.
</div>
{% endif %}
</div>
</div>
<div class="column is-12-mobile is-8-desktop">
<div class="box p-4 shadow-card h-100">
<p class="title is-6 is-size-7-mobile pb-2 mb-4 has-text-danger-dark" style="border-bottom: 1px solid #f0f0f0;">
📞 لیست پیگیری و جلب رضایت (امتیاز ضعیف و بد)
</p>
{% if not low_score_actions %}
<div class="notification is-success is-light has-text-centered mt-5">
خوشبختانه در حال حاضر هیچ مشتری ناراضی برای پیگیری وجود ندارد.
</div>
{% endif %}
<div class="table-container" style="max-height: 400px; overflow-y: auto;">
<table class="table is-fullwidth is-hoverable is-striped">
<thead>
<tr>
<th>مشتری</th>
<th>توضیحات نظرسنجی قبلی</th>
<th>امتیاز</th>
<th>تاریخ ثبت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{% for action in low_score_actions %}
<tr>
<td class="is-vcentered">
<a href="{% url 'admin_panel:customerNew' action.customer.id %}" target="_blank">
{{ action.customer.name_family }}
</a>
</td>
<td class="is-vcentered is-size-7" style="max-width: 250px; white-space: normal;">
{{ action.nazar|default:"-" }}
</td>
<td class="is-vcentered">
{% if action.grade == 15 %}
<span class="tag is-info is-light">متوسط (۱۵)</span>
{% elif action.grade == 10 %}
<span class="tag is-warning is-light">ضعیف (۱۰)</span>
{% elif action.grade == 5 %}
<span class="tag is-danger is-light">بد (۵)</span>
{% else %}
<span class="tag is-info is-light">{{ action.grade }}</span>
{% endif %}
</td>
<td class="is-vcentered is-size-7 ss02">
{{ action.nazarDT|toJalali }}
</td>
<td class="is-vcentered">
<button class="button is-small is-info is-outlined actionBoxOpenB"
action_id="{{ action.id }}"
customer_id="{{ action.customer.id }}">
تماس و بروزرسانی
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<form id="ActionBox" class="modal" method="post" action="">
{% csrf_token %}
<div class="modal-background closeActionBox"></div>
<div class="modal-card">
<header class="modal-card-head has-background-info-light">
<p class="modal-card-title is-size-5 has-text-info-dark">بروزرسانی فرم رضایت‌مندی</p>
<button type="button" class="delete closeActionBox" aria-label="close"></button>
</header>
<section class="modal-card-body">
<div class="columns is-mobile is-multiline">
<div class="field column is-12-desktop is-12-mobile m-0 pb-4" style="border-bottom: 1px dashed #ddd;">
<div class="control" id="customerData">
<p class="has-text-centered is-size-6 has-text-grey mb-2">در حال تماس با:</p>
<p id="csName" class="has-text-centered is-size-4 has-text-weight-bold mb-3"></p>
<p class="has-text-centered is-size-5 csphones">
<span id="csphone1" class="is-hidden hidephone tag is-medium is-success is-light mx-1"></span>
<span id="csphone2" class="is-hidden hidephone tag is-medium is-success is-light mx-1"></span>
<span id="csphone3" class="is-hidden hidephone tag is-medium is-success is-light mx-1"></span>
</p>
</div>
</div>
<div class="field column is-12-desktop is-12-mobile mt-4 m-0">
<div class="control">
<label class="label mb-1">نتیجه تماس و نظر جدید مشتری:</label>
<p class="is-size-7 has-text-grey mb-2">توضیحات و دلایل تغییر نظر مشتری را اینجا ثبت کنید.</p>
<textarea id="csDesc" required name="csDesc" class="textarea" placeholder="پس از تماس و پیگیری، مشکل برطرف شد و مشتری اعلام رضایت کرد..."></textarea>
</div>
</div>
<div class="field column is-12-desktop is-12-mobile m-0">
<label class="label">امتیاز مشتری</label>
<div class="control">
<div class="select" style="width: 100%;">
<select id="csGrade" required name="csGrade" style="width: 100%;">
<option value="">انتخاب کنید</option>
<option value="20">عالی</option>
<option value="15">متوسط</option>
<option value="10">ضعیف</option>
<option value="5">بد</option>
<option value="-1">عدم موفقیت در انجام نظرسنجی (تعویق ۳ روز)</option>
<option value="-2">کنسل کردن این نظرسنجی</option>
</select>
</div>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<span class="button closeActionBox">انصراف</span>
<input type="submit" class="button is-success" value="ثبت نتیجه جدید"/>
</footer>
</div>
</form>
<style>
.shadow-card { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); border: 1px solid #f0f0f0; border-radius: 8px; }
.border-left-danger { border-right: 4px solid #f14668 !important; }
.border-left-success { border-right: 4px solid #48c78e !important; }
.border-left-warning { border-right: 4px solid #ffdd57 !important; }
.h-100 { height: 100%; }
.chart-wrapper-medium { position: relative; height: 280px; width: 100%; }
</style>
{% endblock %}
{% block jsEnd %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
$(document).ready(function() {
// ۱. مقداردهی و رسم نمودار دایره‌ای Chart.js
{% if has_chart_data %}
var ctxChart = document.getElementById('satisfactionChart').getContext('2d');
new Chart(ctxChart, {
type: 'doughnut',
data: {
labels: {{ chart_labels_json|safe }},
datasets: [{
data: {{ chart_data_json|safe }},
backgroundColor: ['#48c78e', '#3e8ed0', '#ffdd57', '#f14668'],
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: { font: { family: 'Tahoma, Vazir', size: 12 } }
}
}
}
});
{% endif %}
// ۲. بستن مدال
$(".closeActionBox").click(function(){
$("#ActionBox").removeClass("is-active");
});
// ۳. باز کردن مدال و بارگذاری اطلاعات مشتری و اقدام
$(".actionBoxOpenB").click(function(){
// ریست کردن بخش تلفن‌ها و ورودی‌ها
$(".hidephone").addClass("is-hidden").html('');
$("#csDesc").val("");
$("#csGrade").val("");
var action_id = $(this).attr("action_id");
var customer_id = $(this).attr("customer_id");
// تنظیم آدرس ارسال فرم اکشن
var actionEditUrl = "{% url 'admin_panel:rezayatEdit' 2222 %}";
actionEditUrl = actionEditUrl.replace('2222', action_id);
$("#ActionBox").prop("action", actionEditUrl);
// دریافت مشخصات مشتری
$.getJSON('/panel/CustomerJson/' + customer_id, function(data) {
var CustomerParseData = JSON.parse(data);
$("#csName").html(CustomerParseData[0]['fields']['name_family']);
var csphone1 = CustomerParseData[0]['fields']['phone1'];
var csphone2 = CustomerParseData[0]['fields']['phone2'];
var csphone3 = CustomerParseData[0]['fields']['phone3'];
if(csphone1 && csphone1.length > 4){
$("#csphone1").html('<a href="tel:' + csphone1 + '">' + csphone1 + '</a>').removeClass("is-hidden");
}
if(csphone2 && csphone2.length > 4){
$("#csphone2").html('<a href="tel:' + csphone2 + '">' + csphone2 + '</a>').removeClass("is-hidden");
}
if(csphone3 && csphone3.length > 4){
$("#csphone3").html('<a href="tel:' + csphone3 + '">' + csphone3 + '</a>').removeClass("is-hidden");
}
});
// دریافت رکورد نظرسنجی قبلی
$.getJSON('/panel/ActionJson/' + action_id, function(data) {
var ActionParseData = JSON.parse(data);
var previousComment = ActionParseData[0]['fields']['nazar'];
if(previousComment) {
$("#csDesc").val("نظر قبلی مشتری: " + previousComment + "\n\n--- نتیجه پیگیری جدید ---\n");
}
$("#ActionBox").addClass("is-active");
});
});
});
</script>
{% endblock %}
+1
View File
@@ -13,6 +13,7 @@ urlpatterns = [
path('report/', views.report, name='report'),
path('report/inventory-movements/', views.inventory_movements_report, name='inventory_movements_report'),
path('report/dashboard/', views.financial_dashboard, name='financial_dashboard'),
path('report/rezayat_dashboard/', views.rezayat_dashboard, name='rezayat_dashboard'),
path('hesabdari/', views.hesabdari, name='hesabdari'),
path('hesabdari/sarresid_nashode/', views.hesabdari_residnashone, name='hesabdari_residnashone'),
path('hesabdari/check/pass/', views.hesabdari_checkPass, name='hesabdari_check_pass'),
+139 -19
View File
@@ -13,7 +13,7 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest # بخشBadRequest جدید
from django.db import transaction, connection
from django.db.models import Count, Sum, Q
from django.db.models import Count, Sum, Q, Avg
from django.contrib.auth.models import Permission, User
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, permission_required
@@ -1930,11 +1930,39 @@ def customerHesabdariAdd(request ,c_id):
@login_required(login_url='/panel/p_login/')
def customerAction(request ,c_id):
customer = Customer.objects.get(id=c_id)
actions = Action.objects.filter(customer=customer).order_by("-createdDT")
def customerAction(request, c_id):
# استفاده از get_object_or_404 برای امنیت بیشتر
customer = get_object_or_404(Customer, id=c_id)
# دریافت پارامترهای فیلتر از URL (پیش‌فرض: all)
filter_type = request.GET.get('actionType', 'all')
filter_status = request.GET.get('status', 'all')
# کوئری پایه برای اقدامات این مشتری
actions = Action.objects.filter(customer=customer)
# اعمال فیلتر بر اساس نوع اقدام (بازدید، نصب و ...)
if filter_type != 'all':
actions = actions.filter(actionType=filter_type)
# اعمال فیلتر بر اساس وضعیت (انجام شده، در انتظار و ...)
if filter_status != 'all':
actions = actions.filter(status=filter_status)
# مرتب‌سازی نزولی بر اساس تاریخ ایجاد
actions = actions.order_by("-createdDT")
allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat')
data = {'customer': customer, 'actions': actions, 'allUsers': allUsers}
data = {
'customer': customer,
'actions': actions,
'allUsers': allUsers,
# ارسال مقادیر فیلتر به قالب برای حفظ وضعیت انتخاب‌شده در دراپ‌داون‌ها
'filter_type': filter_type,
'filter_status': filter_status
}
return render(request, 'admin_panel/customerAction.html', data)
@login_required(login_url='/panel/p_login/')
@@ -2089,16 +2117,40 @@ def ActionListUserToday(request, u_id):
data = {'actions': actions, 'allUsers': allUsers, 'u_id': u_id, "unixADT": unixADT, "status": status, 'actionPr': actionPr}
return render(request, 'admin_panel/ActionList.html', data)
import datetime
from django.db.models import Q
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from .models import Action
@login_required(login_url='/panel/p_login/')
def rezayat(request):
dateTimeAction = request.GET.get('dateTime', None)
statusRezayat = request.GET.get('status', None)
status='finish'
if statusRezayat=='finish':
actions = Action.objects.exclude(actionType='vosol').exclude(Q(nazar='') | Q(nazar=None)).filter(status='finish').order_by("-id")[:50]
statusRezayat = request.GET.get('status', 'waite')
now = datetime.datetime.now()
# محاسبه بازه‌های زمانی ۳۰ روز و ۶۰ روز قبل
dt_30_days_ago = now - datetime.timedelta(days=30)
dt_60_days_ago = now - datetime.timedelta(days=60)
if statusRezayat == 'finish':
# نمایش نظرسنجی‌های تعیین‌تکلیف شده (ثبت شده یا کنسل شده)
actions = Action.objects.exclude(actionType='vosol').filter(
status='finish',
nazarDT__isnull=False
).order_by("-nazarDT")[:500]
else:
statusRezayat='waite'
actions = Action.objects.filter(Q(nazar='') | Q(nazar=None) & Q(status='finish')).exclude(actionType='vosol').order_by("id")[:50]
statusRezayat = 'waite'
# نمایش نظرسنجی‌های در انتظار با اعمال شرط عدم انقضای زمان
actions = Action.objects.exclude(actionType='vosol').filter(
status='finish',
nazarDT__isnull=True, # نظرسنجی هنوز نهایی نشده
createdDT__gte=dt_60_days_ago # حداکثر ۶۰ روز از ایجاد گذشته باشد
).filter(
Q(finishDateTime__isnull=True) | Q(finishDateTime__gte=dt_30_days_ago) # حداکثر ۳۰ روز از اتمام گذشته باشد
).filter(
Q(nextNazarDT__isnull=True) | Q(nextNazarDT__lte=now) # موعد پیگیری رسیده یا تعویقی نداشته باشد
).order_by("-id")[:500]
data = {'actions': actions, "status": statusRezayat}
return render(request, 'admin_panel/customerRezayat.html', data)
@@ -2107,19 +2159,87 @@ def rezayat(request):
def rezayatEdit(request, ac_id):
csGrade = request.POST.get('csGrade', None)
csDesc = request.POST.get('csDesc', None)
if csDesc and csGrade:
action = Action.objects.get(id=ac_id)
action.nazar = csDesc
action.grade = csGrade
action.nazarDT = datetime.datetime.now()
action = get_object_or_404(Action, id=ac_id)
if csGrade == '-1':
# عدم موفقیت: تنظیم nextNazarDT برای ۳ روز بعد (nazarDT پر نمی‌شود تا نهایی نشود)
action.nazar = csDesc
action.grade = int(csGrade)
action.nextNazarDT = datetime.datetime.now() + datetime.timedelta(days=3)
elif csGrade == '-2':
# کنسل کردن: پر کردن nazarDT برای خروج کامل از لیست کارهای در انتظار
action.nazar = csDesc
action.grade = int(csGrade)
action.nazarDT = datetime.datetime.now()
action.nextNazarDT = None
else:
# ثبت امتیاز عادی
action.nazar = csDesc
action.grade = int(csGrade)
action.nazarDT = datetime.datetime.now()
action.nextNazarDT = None
action.save()
return redirect('/panel/rezayat/')
else:
return HttpResponse('لطفا همه فیلد ها را پر کنید')
#data = {'actions': actions, "status": status}
#return render(request, 'admin_panel/customerRezayat.html', data)
return HttpResponse('لطفا همه فیلد ها را پر کنید')
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def rezayat_dashboard(request):
# دریافت تمامی نظرسنجی‌های ثبت شده معتبر (که تاریخ نظرسنجی دارند)
valid_surveys = Action.objects.filter(
nazarDT__isnull=False,
grade__in=[5, 10, 15, 20]
)
# ۱. محاسبات شاخص‌های کلیدی (KPIs)
total_surveys = valid_surveys.count()
avg_grade = valid_surveys.aggregate(Avg('grade'))['grade__avg'] or 0
# ۲. استخراج داده‌ها برای نمودار توزیع رضایت
grade_distribution = valid_surveys.values('grade').annotate(count=Count('id'))
chart_dict = {20: 0, 15: 0, 10: 0, 5: 0}
for item in grade_distribution:
g = item['grade']
if g in chart_dict:
chart_dict[g] = item['count']
chart_labels = ['عالی (۲۰)', 'متوسط (۱۵)', 'ضعیف (۱۰)', 'بد (۵)']
chart_data_list = [chart_dict[20], chart_dict[15], chart_dict[10], chart_dict[5]]
# ۳. تبدیل امن آرایه‌ها به JSON برای Chart.js
chart_labels_json = json.dumps(chart_labels, ensure_ascii=False)
chart_data_json = json.dumps(chart_data_list)
has_chart_data = sum(chart_data_list) > 0
# ۴. واکشی لیست مشتریان نیازمند پیگیری (شامل متوسط ۱۵، ضعیف ۱۰ و بد ۵)
low_score_actions = Action.objects.filter(
nazarDT__isnull=False,
grade__in=[5, 10, 15] # <-- امتیاز ۱۵ (متوسط) نیز اضافه شد
).order_by('-nazarDT')
allUsers = User.objects.filter().exclude(username='admin').exclude(username='fesahat')
context = {
'total_surveys': total_surveys,
'avg_grade': round(avg_grade, 1),
'chart_labels_json': chart_labels_json,
'chart_data_json': chart_data_json,
'has_chart_data': has_chart_data,
'low_score_actions': low_score_actions,
# تعداد کل موارد نیازمند پیگیری (متوسط + ضعیف + بد)
'unhappy_count': chart_dict[15] + chart_dict[10] + chart_dict[5],
'allUsers': allUsers,
}
return render(request, 'admin_panel/report_rezayat.html', context)
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewStore', raise_exception=True)
def passCheck(request, articl_id):