fix nazar sanji and eghdamha
This commit is contained in:
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),
|
||||
),
|
||||
]
|
||||
@@ -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 %}
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user