diff --git a/admin_panel/migrations/0055_action_nextnazardt.py b/admin_panel/migrations/0055_action_nextnazardt.py
new file mode 100644
index 0000000..65c6d80
--- /dev/null
+++ b/admin_panel/migrations/0055_action_nextnazardt.py
@@ -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),
+ ),
+ ]
diff --git a/admin_panel/models.py b/admin_panel/models.py
index 0af3a32..b28861c 100644
--- a/admin_panel/models.py
+++ b/admin_panel/models.py
@@ -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)
diff --git a/admin_panel/templates/admin_panel/base.html b/admin_panel/templates/admin_panel/base.html
index 2b5f305..0f6f888 100644
--- a/admin_panel/templates/admin_panel/base.html
+++ b/admin_panel/templates/admin_panel/base.html
@@ -102,6 +102,11 @@
داشبورد مدیریتی
+
+
+ گزارش نظر سنجی ها
+
+
diff --git a/admin_panel/templates/admin_panel/customerActionInc.html b/admin_panel/templates/admin_panel/customerActionInc.html
index b02f459..412d0d5 100644
--- a/admin_panel/templates/admin_panel/customerActionInc.html
+++ b/admin_panel/templates/admin_panel/customerActionInc.html
@@ -5,8 +5,62 @@
{% endwith %}
{% if customer %}
+
ایجاد وظیفه جدید
{% endif %}
+
+ {% if not actions and not actionPr %}
+
+ هیچ اقدامی با مشخصات انتخابشده یافت نشد!
+
+ {% endif %}
+
{% for action in actions %}
@@ -35,34 +89,28 @@
-
+
{{ action.actionDateTime | toJalali }}
-
-
-
+
{{ action.forUser.first_name }} {{ action.forUser.last_name }}
-
{% endfor %}
+
{% if actionPr %}
-
کارهای مانده از روزهای قبل
+
کارهای مانده از روزهای قبل
{% for action in actionPr %}
@@ -89,26 +137,23 @@
نتیجه
{% if action.result %} {{ action.result }} {% else %}هنوز گزارش یا نتیجه ای ثبت نشده!{% endif %}
-
-
+
{{ action.actionDateTime | toJalali }}
-
-
-
+
{{ action.forUser.first_name }} {{ action.forUser.last_name }}
-
{% endfor %}
@@ -127,7 +172,7 @@
@@ -135,16 +180,17 @@
نوع اقدام
-
- انتخاب کنید
+ انتخاب کنید
بازدید
نصب
وصول مطالبات
+ هماهنگی
+ تماس ورودی
+ تماس خروجی
-
@@ -159,20 +205,17 @@
توضیحات برای یادآوری حین انجام کار:
-
راجب کاری که در آینده میخواهید انجام دهید توضیحاتی را بنویسید
-
-
+
راجب کاری که در آینده میخواهید انجام دهید توضیحاتی را بنویسید
+
توسط
-
- انتخاب کنید
-
+ انتخاب کنید
{% if perms.admin_panel.ViewStore %}
{% for auser in allUsers %}
{{ auser.first_name }} {{ auser.last_name }}
@@ -183,18 +226,15 @@
{% endif %}
-
@@ -209,25 +249,20 @@
نتیجه
-
نتیجه کار یا پیگیری که انجام داده اید را بنویسید
+
نتیجه کار یا پیگیری که انجام داده اید را بنویسید
-
-
+
\ No newline at end of file
diff --git a/admin_panel/templates/admin_panel/customerRezayatinc.html b/admin_panel/templates/admin_panel/customerRezayatinc.html
index e6148b6..d12b480 100644
--- a/admin_panel/templates/admin_panel/customerRezayatinc.html
+++ b/admin_panel/templates/admin_panel/customerRezayatinc.html
@@ -61,30 +61,28 @@
-
-
-
محمد جواد فصاحت
-
+
محمد جواد فصاحت
+
-
+
+
@@ -119,23 +117,19 @@
متوسط
ضعیف
بد
+
عدم موفقیت در انجام نظرسنجی (تعویق ۳ روز)
+
کنسل کردن این نظرسنجی
-
-
@@ -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);
-
\ No newline at end of file
diff --git a/admin_panel/templates/admin_panel/report_rezayat.html b/admin_panel/templates/admin_panel/report_rezayat.html
new file mode 100644
index 0000000..9c303ca
--- /dev/null
+++ b/admin_panel/templates/admin_panel/report_rezayat.html
@@ -0,0 +1,290 @@
+{% extends "./base.html" %}
+{% load custom_tags %}
+{% load static %}
+
+{% block bodyPage %}
+
+
+
+
+
داشبورد کنترل کیفیت و رضایت مشتریان
+
مانیتورینگ بازخوردها و پیگیری مشتریان ناراضی جهت جلب رضایت مجدد
+
+
+
+
+
+
+
+
+
+
کل نظرسنجیهای انجام شده
+
{{ total_surveys }} مورد
+
+
+
+
+
+
+
+
+
+
+
میانگین امتیاز سیستم
+
{{ avg_grade }} از ۲۰
+
+
+
+
+
+
+
+
+
+
+
مشتریان ناراضی (نیازمند پیگیری)
+
{{ unhappy_count }} نفر
+
+
+
+
+
+
+
+
+
+
+
+
+ 📊 توزیع سطوح رضایتمندی
+
+ {% if has_chart_data %}
+
+
+
+ {% else %}
+
+ هنوز دادهای برای رسم نمودار رضایتمندی ثبت نشده است.
+
+ {% endif %}
+
+
+
+
+
+
+ 📞 لیست پیگیری و جلب رضایت (امتیاز ضعیف و بد)
+
+
+ {% if not low_score_actions %}
+
+ خوشبختانه در حال حاضر هیچ مشتری ناراضی برای پیگیری وجود ندارد.
+
+ {% endif %}
+
+
+
+
+
+ مشتری
+ توضیحات نظرسنجی قبلی
+ امتیاز
+ تاریخ ثبت
+ عملیات
+
+
+
+ {% for action in low_score_actions %}
+
+
+
+ {{ action.customer.name_family }}
+
+
+
+ {{ action.nazar|default:"-" }}
+
+
+ {% if action.grade == 15 %}
+ متوسط (۱۵)
+ {% elif action.grade == 10 %}
+ ضعیف (۱۰)
+ {% elif action.grade == 5 %}
+ بد (۵)
+ {% else %}
+ {{ action.grade }}
+ {% endif %}
+
+
+ {{ action.nazarDT|toJalali }}
+
+
+
+ تماس و بروزرسانی
+
+
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block jsEnd %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/admin_panel/urls.py b/admin_panel/urls.py
index 8848e3e..29d7a65 100644
--- a/admin_panel/urls.py
+++ b/admin_panel/urls.py
@@ -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'),
diff --git a/admin_panel/views.py b/admin_panel/views.py
index 95e9834..f57b11a 100644
--- a/admin_panel/views.py
+++ b/admin_panel/views.py
@@ -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):