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) result = models.CharField(max_length=500, null=True, blank=True)
status = models.CharField(max_length=50, 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) 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) nazarDT = models.DateTimeField(null=True, blank=True, default=None)
grade = models.IntegerField(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 href="{% url 'admin_panel:financial_dashboard' %}" style="padding-right: 10px;" class="navbar-item">
داشبورد مدیریتی داشبورد مدیریتی
</a> </a>
<a href="{% url 'admin_panel:rezayat_dashboard' %}" style="padding-right: 10px;" class="navbar-item">
گزارش نظر سنجی ها
</a>
</div> </div>
</div> </div>
@@ -5,8 +5,62 @@
{% endwith %} {% endwith %}
{% if customer %} {% 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> <span id="showActionBox" class="button is-info mb-5">ایجاد وظیفه جدید</span>
{% endif %} {% endif %}
{% if not actions and not actionPr %}
<div class="notification is-warning is-light has-text-centered">
هیچ اقدامی با مشخصات انتخاب‌شده یافت نشد!
</div>
{% endif %}
{% for action in actions %} {% 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 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"> <div class="column is-12-mobile is-12-desktop actionBoxDesc actionBoxOpenB">
@@ -35,34 +89,28 @@
</div> </div>
<div class="column is-4-mobile is-4-desktop"> <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> <span class="ss02">{{ action.actionDateTime | toJalali }}</span>
</div> </div>
</div> </div>
<div class="column is-8-mobile is-4-desktop"> <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 %}"> <a href="{% url 'admin_panel:customerNew' action.customer.id %}">
<span>{{ action.customer.name_family }}</span> <span>{{ action.customer.name_family }}</span>
</a> </a>
</div> </div>
</div> </div>
<div class="column is-12-mobile is-4-desktop is-hidden-mobile"> <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> <span>{{ action.forUser.first_name }} {{ action.forUser.last_name }}</span>
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% if actionPr %} {% if actionPr %}
<hr> <hr>
<p style=" <p style="text-align: center; margin-bottom: 20px; color: #d90000; font-size: 18px;"> کارهای مانده از روزهای قبل</p>
text-align: center;
margin-bottom: 20px;
color: #d90000;
font-size: 18px;
"> کارهای مانده از روزهای قبل</p>
{% for action in actionPr %} {% for action in actionPr %}
<div customer_id='{{ action.customer.id }}' action_id='{{ action.id }}' class="columns is-mobile is-multiline is-gapless actionBox {{ bgBox }}"> <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 class="is-result">نتیجه</span>
<span>{% if action.result %} {{ action.result }} {% else %}هنوز گزارش یا نتیجه ای ثبت نشده!{% endif %}</span> <span>{% if action.result %} {{ action.result }} {% else %}هنوز گزارش یا نتیجه ای ثبت نشده!{% endif %}</span>
</div> </div>
</div> </div>
<div class="column is-4-mobile is-4-desktop"> <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> <span class="ss02">{{ action.actionDateTime | toJalali }}</span>
</div> </div>
</div> </div>
<div class="column is-8-mobile is-4-desktop"> <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 %}"> <a href="{% url 'admin_panel:customerNew' action.customer.id %}">
<span>{{ action.customer.name_family }}</span> <span>{{ action.customer.name_family }}</span>
</a> </a>
</div> </div>
</div> </div>
<div class="column is-12-mobile is-4-desktop is-hidden-mobile"> <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> <span>{{ action.forUser.first_name }} {{ action.forUser.last_name }}</span>
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@@ -127,7 +172,7 @@
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title is-hidden">ایجاد وظیفه</p> <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> </header>
<section class="modal-card-body"> <section class="modal-card-body">
@@ -135,16 +180,17 @@
<div class="field column is-6-desktop is-12-mobile m-0"> <div class="field column is-6-desktop is-12-mobile m-0">
<label class="label">نوع اقدام</label> <label class="label">نوع اقدام</label>
<div class="control"> <div class="control">
<div class="select" style="width: 100%;"> <div class="select" style="width: 100%;">
<select id="actionType" required name="actionType" style="width: 100%;"> <select id="actionType" required name="actionType" style="width: 100%;">
<option>انتخاب کنید</option> <option value="">انتخاب کنید</option>
<option value="bazdid">بازدید</option> <option value="bazdid">بازدید</option>
<option value="nasb">نصب</option> <option value="nasb">نصب</option>
<option value="vosol">وصول مطالبات</option> <option value="vosol">وصول مطالبات</option>
<option value="hamahangi">هماهنگی</option>
<option value="inCall">تماس ورودی</option>
<option value="outCall">تماس خروجی</option>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
@@ -159,20 +205,17 @@
<div class="field column is-12-desktop is-12-mobile m-0"> <div class="field column is-12-desktop is-12-mobile m-0">
<div class="control"> <div class="control">
<label class="label mb-0">توضیحات برای یادآوری حین انجام کار:</label> <label class="label mb-0">توضیحات برای یادآوری حین انجام کار:</label>
<p class="is-size-7 ">راجب کاری که در آینده میخواهید انجام دهید توضیحاتی را بنویسید</p> <p class="is-size-7">راجب کاری که در آینده میخواهید انجام دهید توضیحاتی را بنویسید</p>
<textarea id="desc" required name="desc" class="textarea " placeholder="مثلا: بامشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea> <textarea id="desc" required name="desc" class="textarea" placeholder="مثلا: بامشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea>
</div> </div>
</div> </div>
<div class="field column is-6-desktop is-12-mobile m-0"> <div class="field column is-6-desktop is-12-mobile m-0">
<label class="label">توسط</label> <label class="label">توسط</label>
<div class="control"> <div class="control">
<div class="select" style="width: 100%;"> <div class="select" style="width: 100%;">
<select id="userID" required name="userID" style="width: 100%;"> <select id="userID" required name="userID" style="width: 100%;">
<option>انتخاب کنید</option> <option value="">انتخاب کنید</option>
<!-- perms.admin_panel.ViewStore !-->
{% if perms.admin_panel.ViewStore %} {% if perms.admin_panel.ViewStore %}
{% for auser in allUsers %} {% for auser in allUsers %}
<option value="{{ auser.id }}">{{ auser.first_name }} {{ auser.last_name }}</option> <option value="{{ auser.id }}">{{ auser.first_name }} {{ auser.last_name }}</option>
@@ -183,18 +226,15 @@
{% endif %} {% endif %}
</select> </select>
</div> </div>
</div> </div>
</div> </div>
<div class="field column is-12-desktop is-12-mobile m-0"> <div class="field column is-12-desktop is-12-mobile m-0">
<div class="control"> <div class="control">
<label class="checkbox"> <label class="checkbox">
<input name="is_done" id="checkIsDone" type="checkbox"> <input name="is_done" id="checkIsDone" type="checkbox">
این کار انجام شده این کار انجام شده
</label> </label>
</div> </div>
</div> </div>
@@ -209,25 +249,20 @@
<div class="field column is-12-desktop is-12-mobile m-0 showWhenDone is-hidden"> <div class="field column is-12-desktop is-12-mobile m-0 showWhenDone is-hidden">
<div class="control"> <div class="control">
<label class="label mb-0">نتیجه</label> <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> <textarea name="result" id="actionResult" class="textarea" placeholder="نتیجه فعالیت انجام شده را بنویسید"></textarea>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<span class="button">حذف</span> <button type="button" class="button closeActionBox">لغو</button>
<span class="button">لغو</span>
<!--
<button class="button closeActionBox">بستن</button>!-->
<input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/> <input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/>
</footer> </footer>
</div> </div>
</form> </form>
<script> <script>
$(".closeActionBox").click(function(){ $(".closeActionBox").click(function(){
$("#ActionBox").removeClass("is-active"); $("#ActionBox").removeClass("is-active");
@@ -236,7 +271,7 @@
$("#showActionBox").click(function(){ $("#showActionBox").click(function(){
$("#ActionBox").addClass("is-active"); $("#ActionBox").addClass("is-active");
{% if customer %} {% if customer %}
var NewActionUrl ="{% url 'admin_panel:customerNewAction' customer.id %}"; var NewActionUrl = "{% url 'admin_panel:customerNewAction' customer.id %}";
$("#ActionBox").prop("action", NewActionUrl); $("#ActionBox").prop("action", NewActionUrl);
{% endif %} {% endif %}
$("#actionType").val(''); $("#actionType").val('');
@@ -253,7 +288,7 @@
$("#checkIsDone").change(function() { $("#checkIsDone").change(function() {
if(this.checked) { if(this.checked) {
$(".showWhenDone").removeClass("is-hidden"); $(".showWhenDone").removeClass("is-hidden");
}else{ } else {
$(".showWhenDone").addClass("is-hidden"); $(".showWhenDone").addClass("is-hidden");
} }
}); });
@@ -266,9 +301,9 @@
actionEditUrl = actionEditUrl.replace('1111', customer_id); actionEditUrl = actionEditUrl.replace('1111', customer_id);
actionEditUrl = actionEditUrl.replace('2222', action_id); actionEditUrl = actionEditUrl.replace('2222', action_id);
$("#ActionBox").prop("action", actionEditUrl); $("#ActionBox").prop("action", actionEditUrl);
$.getJSON('/panel/ActionJson/'+action_id, function(data) { $.getJSON('/panel/ActionJson/' + action_id, function(data) {
ActionParseData = JSON.parse(data); ActionParseData = JSON.parse(data);
console.log(ActionParseData) console.log(ActionParseData);
var actionDateTime0 = new Date(ActionParseData[0]['fields']['actionDateTime']); var actionDateTime0 = new Date(ActionParseData[0]['fields']['actionDateTime']);
var finishDateTime = null; var finishDateTime = null;
if(ActionParseData[0]['fields']['finishDateTime'] != null){ if(ActionParseData[0]['fields']['finishDateTime'] != null){
@@ -286,7 +321,7 @@
if(ActionParseData[0]['fields']['status'] == 'finish'){ if(ActionParseData[0]['fields']['status'] == 'finish'){
$("#checkIsDone").prop('checked', true); $("#checkIsDone").prop('checked', true);
$(".showWhenDone").removeClass("is-hidden"); $(".showWhenDone").removeClass("is-hidden");
}else{ } else {
$("#checkIsDone").prop('checked', false); $("#checkIsDone").prop('checked', false);
$(".showWhenDone").addClass("is-hidden"); $(".showWhenDone").addClass("is-hidden");
} }
@@ -328,8 +363,5 @@
enabled: false, enabled: false,
} }
}, },
}) });
//.setDate(1382976091100);
</script> </script>
@@ -61,30 +61,28 @@
<div class="modal-card"> <div class="modal-card">
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title is-hidden">ایجاد وظیفه</p> <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> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div class="columns is-mobile is-multiline"> <div class="columns is-mobile is-multiline">
<div class="field column is-12-desktop is-12-mobile m-0"> <div class="field column is-12-desktop is-12-mobile m-0">
<div class="control" id="customerData"> <div class="control" id="customerData">
<p id="csName" class="has-text-centered is-size-4 ">محمد جواد فصاحت</p> <p id="csName" class="has-text-centered is-size-4">محمد جواد فصاحت</p>
<p class="has-text-centered is-size-5 csphones"> <p class="has-text-centered is-size-5 csphones">
<span id="csphone1" class="is-hidden hidephone"></span> <span id="csphone1" class="is-hidden hidephone"></span>
<span id="csphone2" 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> </div>
<div class="field column is-12-desktop is-12-mobile m-0"> <div class="field column is-12-desktop is-12-mobile m-0">
<div class="control"> <div class="control">
<label class="label mb-0">نکات و نظر مشتری:</label> <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> <textarea id="csDesc" required name="csDesc" class="textarea " placeholder="مثلا: بامشتری تماس بگیرم برای هماهنگی تعویض فیلتر دستگاه"></textarea>
</div> </div>
</div> </div>
@@ -119,23 +117,19 @@
<option value="15">متوسط</option> <option value="15">متوسط</option>
<option value="10">ضعیف</option> <option value="10">ضعیف</option>
<option value="5">بد</option> <option value="5">بد</option>
<option value="-1">عدم موفقیت در انجام نظرسنجی (تعویق ۳ روز)</option>
<option value="-2">کنسل کردن این نظرسنجی</option>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<span class="button">حذف</span> <span class="button closeActionBox">لغو</span>
<span class="button">لغو</span>
<!--
<button class="button closeActionBox">بستن</button>!-->
<input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/> <input id="sabtBtn05" type="submit" class="button is-success" value="ثبت"/>
</footer> </footer>
</div> </div>
</form> </form>
@@ -173,14 +167,11 @@
$(".hidephone").addClass("is-hidden"); $(".hidephone").addClass("is-hidden");
$("#csDesc").val("") $("#csDesc").val("")
$("#csGrade").val("") $("#csGrade").val("")
//$("#sabtBtn05").val("ویرایش");
var actionEditUrl = "{% url 'admin_panel:rezayatEdit' 2222 %}"; var actionEditUrl = "{% url 'admin_panel:rezayatEdit' 2222 %}";
var action_id = $(this).parent().attr("action_id"); var action_id = $(this).parent().attr("action_id");
var customer_id = $(this).parent().attr("customer_id"); var customer_id = $(this).parent().attr("customer_id");
//actionEditUrl = actionEditUrl.replace('1111', customer_id);
actionEditUrl = actionEditUrl.replace('2222', action_id); actionEditUrl = actionEditUrl.replace('2222', action_id);
$("#csphone1")
$.getJSON('/panel/CustomerJson/'+customer_id, function(data) { $.getJSON('/panel/CustomerJson/'+customer_id, function(data) {
CustomerParseData = JSON.parse(data); CustomerParseData = JSON.parse(data);
$("#csName").html(CustomerParseData[0]['fields']['name_family']); $("#csName").html(CustomerParseData[0]['fields']['name_family']);
@@ -204,12 +195,7 @@
$("#ActionBox").prop("action", actionEditUrl); $("#ActionBox").prop("action", actionEditUrl);
$.getJSON('/panel/ActionJson/'+action_id, function(data) { $.getJSON('/panel/ActionJson/'+action_id, function(data) {
ActionParseData = JSON.parse(data); ActionParseData = JSON.parse(data);
//console.log(ActionParseData)
$("#ActionBox").addClass("is-active"); $("#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']) $("#csDesc").val(ActionParseData[0]['fields']['nazar'])
$("#csGrade").val(ActionParseData[0]['fields']['grade']) $("#csGrade").val(ActionParseData[0]['fields']['grade'])
}); });
@@ -250,7 +236,5 @@
} }
}, },
}) })
//.setDate(1382976091100);
</script> </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/', views.report, name='report'),
path('report/inventory-movements/', views.inventory_movements_report, name='inventory_movements_report'), path('report/inventory-movements/', views.inventory_movements_report, name='inventory_movements_report'),
path('report/dashboard/', views.financial_dashboard, name='financial_dashboard'), 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/', views.hesabdari, name='hesabdari'),
path('hesabdari/sarresid_nashode/', views.hesabdari_residnashone, name='hesabdari_residnashone'), path('hesabdari/sarresid_nashode/', views.hesabdari_residnashone, name='hesabdari_residnashone'),
path('hesabdari/check/pass/', views.hesabdari_checkPass, name='hesabdari_check_pass'), 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.views.decorators.http import require_POST
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest # بخشBadRequest جدید from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest # بخشBadRequest جدید
from django.db import transaction, connection 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.models import Permission, User
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, permission_required 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/') @login_required(login_url='/panel/p_login/')
def customerAction(request ,c_id): def customerAction(request, c_id):
customer = Customer.objects.get(id=c_id) # استفاده از get_object_or_404 برای امنیت بیشتر
actions = Action.objects.filter(customer=customer).order_by("-createdDT") 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') 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) return render(request, 'admin_panel/customerAction.html', data)
@login_required(login_url='/panel/p_login/') @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} data = {'actions': actions, 'allUsers': allUsers, 'u_id': u_id, "unixADT": unixADT, "status": status, 'actionPr': actionPr}
return render(request, 'admin_panel/ActionList.html', data) 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/') @login_required(login_url='/panel/p_login/')
def rezayat(request): def rezayat(request):
dateTimeAction = request.GET.get('dateTime', None) statusRezayat = request.GET.get('status', 'waite')
statusRezayat = request.GET.get('status', None) now = datetime.datetime.now()
status='finish'
if statusRezayat=='finish': # محاسبه بازه‌های زمانی ۳۰ روز و ۶۰ روز قبل
actions = Action.objects.exclude(actionType='vosol').exclude(Q(nazar='') | Q(nazar=None)).filter(status='finish').order_by("-id")[:50] 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: else:
statusRezayat='waite' statusRezayat = 'waite'
actions = Action.objects.filter(Q(nazar='') | Q(nazar=None) & Q(status='finish')).exclude(actionType='vosol').order_by("id")[:50] # نمایش نظرسنجی‌های در انتظار با اعمال شرط عدم انقضای زمان
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} data = {'actions': actions, "status": statusRezayat}
return render(request, 'admin_panel/customerRezayat.html', data) return render(request, 'admin_panel/customerRezayat.html', data)
@@ -2107,19 +2159,87 @@ def rezayat(request):
def rezayatEdit(request, ac_id): def rezayatEdit(request, ac_id):
csGrade = request.POST.get('csGrade', None) csGrade = request.POST.get('csGrade', None)
csDesc = request.POST.get('csDesc', None) csDesc = request.POST.get('csDesc', None)
if csDesc and csGrade: if csDesc and csGrade:
action = Action.objects.get(id=ac_id) action = get_object_or_404(Action, id=ac_id)
action.nazar = csDesc
action.grade = csGrade if csGrade == '-1':
action.nazarDT = datetime.datetime.now() # عدم موفقیت: تنظیم 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() action.save()
return redirect('/panel/rezayat/') return redirect('/panel/rezayat/')
else: else:
return HttpResponse('لطفا همه فیلد ها را پر کنید') return HttpResponse('لطفا همه فیلد ها را پر کنید')
#data = {'actions': actions, "status": status}
#return render(request, 'admin_panel/customerRezayat.html', data)
@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/') @login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewStore', raise_exception=True) @permission_required('admin_panel.ViewStore', raise_exception=True)
def passCheck(request, articl_id): def passCheck(request, articl_id):