improve entezar bazdid
This commit is contained in:
1 parent
1fd27d0445
commit
2aee85593f
18 files changed
+10271
-7558
No files matched your search
@@ -1,13 +1,19 @@
|
||||
import datetime
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.http import HttpResponse
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils import timezone
|
||||
|
||||
from ..models import Action, Customer
|
||||
from ..sms.triggers import notify_visit_scheduled
|
||||
from ..visit_policy import (REASON_CANCEL, REASON_CREATE, REASON_EDIT,
|
||||
as_aware, normalize_finish_datetime,
|
||||
refresh_last_bazdid, validate_action_payload)
|
||||
|
||||
|
||||
@login_required(login_url='/panel/p_login/')
|
||||
@@ -54,46 +60,68 @@ def customerNazar(request ,c_id):
|
||||
data = {'customer': customer, 'actions': actions, 'allUsers': allUsers}
|
||||
return render(request, 'admin_panel/customer/nazar.html', data)
|
||||
|
||||
def _parse_form_datetime(raw):
|
||||
"""timestamp میلیثانیهای فرم → دیتاتایم aware (یا ``None``).
|
||||
|
||||
کد قبلی ``datetime.datetime.fromtimestamp`` (naive) میساخت که هنگام ذخیره
|
||||
با ``USE_TZ=True`` هشدار ``RuntimeWarning`` میداد؛ اینجا صریح aware میشود.
|
||||
"""
|
||||
try:
|
||||
return as_aware(datetime.datetime.fromtimestamp(int(raw) / 1e3))
|
||||
except (TypeError, ValueError, OverflowError, OSError):
|
||||
return None
|
||||
|
||||
|
||||
def _action_payload_from_request(request):
|
||||
"""استخراج و نرمالسازی ورودی فرم اقدام (مشترک ایجاد و ویرایش)."""
|
||||
is_done = bool(request.POST.get('is_done'))
|
||||
return {
|
||||
'actionType': (request.POST.get('actionType') or '').strip(),
|
||||
'status': Action.Status.FINISH if is_done else Action.Status.WAITE,
|
||||
'actionDateTime': _parse_form_datetime(request.POST.get('dateTimeAnjam')),
|
||||
'finishDateTime': _parse_form_datetime(request.POST.get('FinishDateTime')),
|
||||
'desc': request.POST.get('desc', None),
|
||||
'result': request.POST.get('result', None),
|
||||
'userID': request.POST.get('userID', None),
|
||||
'is_done': is_done,
|
||||
}
|
||||
|
||||
|
||||
def _reject(request, error, redirect_to):
|
||||
"""پیام فارسی خطا به کاربر و بازگشت بدون ذخیرهٔ چیزی."""
|
||||
messages.error(request, '؛ '.join(error.messages))
|
||||
return redirect(redirect_to)
|
||||
|
||||
|
||||
@login_required(login_url='/panel/p_login/')
|
||||
def customerNewAction(request ,c_id):
|
||||
actionType = request.POST.get('actionType', None)
|
||||
dateTimeAnjam = request.POST.get('dateTimeAnjam', None)
|
||||
#return HttpResponse(float(dateTimeAnjam+".0"))
|
||||
payload = _action_payload_from_request(request)
|
||||
try:
|
||||
dateTimeAnjam = datetime.datetime.fromtimestamp(int(dateTimeAnjam)/1e3)
|
||||
except:
|
||||
pass
|
||||
desc = request.POST.get('desc', None)
|
||||
userID = request.POST.get('userID', None)
|
||||
is_done = request.POST.get('is_done', None)
|
||||
result = request.POST.get('result', None)
|
||||
FinishDateTime = request.POST.get('FinishDateTime', None)
|
||||
try:
|
||||
FinishDateTime = datetime.datetime.fromtimestamp(int(FinishDateTime)/1e3)
|
||||
except:
|
||||
pass
|
||||
validate_action_payload(payload)
|
||||
except ValidationError as error:
|
||||
return _reject(request, error, '/panel/customer/' + str(c_id) + '/action')
|
||||
|
||||
customer_obj = Customer.objects.get(id=int(c_id))
|
||||
newAction = Action()
|
||||
newAction.actionType = actionType
|
||||
newAction.actionDateTime = dateTimeAnjam
|
||||
newAction.desc = desc
|
||||
newAction.forUser = User.objects.get(id=int(userID))
|
||||
newAction.actionType = payload['actionType']
|
||||
newAction.actionDateTime = payload['actionDateTime']
|
||||
newAction.desc = payload['desc']
|
||||
newAction.forUser = User.objects.get(id=int(payload['userID']))
|
||||
newAction.customer = customer_obj
|
||||
if is_done:
|
||||
newAction.status = 'finish'
|
||||
newAction.finishDateTime = FinishDateTime
|
||||
newAction.result = result
|
||||
if payload['is_done']:
|
||||
newAction.status = Action.Status.FINISH
|
||||
# تاریخ اتمام خالی نباید None ذخیره شود (سابقه را پاک میکرد).
|
||||
newAction.finishDateTime = normalize_finish_datetime(payload['finishDateTime'])
|
||||
newAction.result = payload['result']
|
||||
else:
|
||||
newAction.status = 'waite'
|
||||
newAction.status = Action.Status.WAITE
|
||||
newAction.save()
|
||||
|
||||
if newAction.status == 'finish' and newAction.actionType == 'bazdid':
|
||||
customer_obj.last_bazdid_dt = newAction.finishDateTime
|
||||
customer_obj.save()
|
||||
# last_bazdid_dt فقط از تابع واحد بازمحاسبه میشود؛ نه از finishDateTime.
|
||||
refresh_last_bazdid(customer_obj, reason=REASON_CREATE, user=request.user)
|
||||
|
||||
# پیامک «بازدید شما برای تاریخ … ثبت شد» فقط برای اقدام از نوع بازدید.
|
||||
if newAction.actionType == 'bazdid':
|
||||
if newAction.actionType == Action.ActionType.BAZDID:
|
||||
notify_visit_scheduled(customer_obj, newAction, created_by=request.user)
|
||||
|
||||
return redirect('/panel/customer/'+str(c_id)+'/action')
|
||||
@@ -116,42 +144,37 @@ def ActionListUser(request, u_id):
|
||||
@login_required(login_url='/panel/p_login/')
|
||||
def customerEditAction(request ,c_id, action_id):
|
||||
|
||||
actionType = request.POST.get('actionType', None)
|
||||
dateTimeAnjam = request.POST.get('dateTimeAnjam', None)
|
||||
print("ok")
|
||||
#return HttpResponse(float(dateTimeAnjam+".0"))
|
||||
payload = _action_payload_from_request(request)
|
||||
try:
|
||||
dateTimeAnjam = datetime.datetime.fromtimestamp(int(dateTimeAnjam)/1e3)
|
||||
except:
|
||||
pass
|
||||
desc = request.POST.get('desc', None)
|
||||
userID = request.POST.get('userID', None)
|
||||
is_done = request.POST.get('is_done', None)
|
||||
result = request.POST.get('result', None)
|
||||
FinishDateTime = request.POST.get('FinishDateTime', None)
|
||||
try:
|
||||
FinishDateTime = datetime.datetime.fromtimestamp(int(FinishDateTime)/1e3)
|
||||
except:
|
||||
pass
|
||||
validate_action_payload(payload)
|
||||
except ValidationError as error:
|
||||
return _reject(
|
||||
request, error,
|
||||
request.META.get('HTTP_REFERER', '/panel/customer/' + str(c_id) + '/action'),
|
||||
)
|
||||
|
||||
customer_obj = Customer.objects.get(id=int(c_id))
|
||||
newAction = Action.objects.get(id=action_id)
|
||||
newAction.actionType = actionType
|
||||
newAction.actionDateTime = dateTimeAnjam
|
||||
newAction.desc = desc
|
||||
newAction.forUser = User.objects.get(id=int(userID))
|
||||
was_done = newAction.status == Action.Status.FINISH
|
||||
|
||||
newAction.actionType = payload['actionType']
|
||||
newAction.actionDateTime = payload['actionDateTime']
|
||||
newAction.desc = payload['desc']
|
||||
newAction.forUser = User.objects.get(id=int(payload['userID']))
|
||||
newAction.customer = customer_obj
|
||||
if is_done:
|
||||
newAction.status = 'finish'
|
||||
newAction.finishDateTime = FinishDateTime
|
||||
newAction.result = result
|
||||
if payload['is_done']:
|
||||
newAction.status = Action.Status.FINISH
|
||||
newAction.finishDateTime = normalize_finish_datetime(payload['finishDateTime'])
|
||||
newAction.result = payload['result']
|
||||
else:
|
||||
newAction.status = 'waite'
|
||||
newAction.status = Action.Status.WAITE
|
||||
newAction.save()
|
||||
|
||||
if newAction.status == 'finish' and newAction.actionType == 'bazdid':
|
||||
customer_obj.last_bazdid_dt = newAction.finishDateTime
|
||||
customer_obj.save()
|
||||
# بازمحاسبهٔ واحد: هم پس از ویرایش، هم وقتی رکورد از «انجامشده» برگشت میخورد
|
||||
# (تا مقداری که این رکورد ساخته بود با منبع حقیقت همراستا شود).
|
||||
reason = REASON_CANCEL if (was_done and not payload['is_done']) else REASON_EDIT
|
||||
refresh_last_bazdid(customer_obj, reason=reason, user=request.user)
|
||||
|
||||
#return redirect('/panel/customer/'+str(c_id)+'/action')
|
||||
return redirect(request.META.get('HTTP_REFERER', 'redirect_if_referer_not_found'))
|
||||
|
||||
@@ -205,7 +228,7 @@ def ActionListUserToday(request, u_id):
|
||||
@login_required(login_url='/panel/p_login/')
|
||||
def rezayat(request):
|
||||
statusRezayat = request.GET.get('status', 'waite')
|
||||
now = datetime.datetime.now()
|
||||
now = timezone.now()
|
||||
|
||||
# محاسبه بازههای زمانی ۳۰ روز و ۶۰ روز قبل
|
||||
dt_30_days_ago = now - datetime.timedelta(days=30)
|
||||
@@ -246,20 +269,20 @@ def rezayatEdit(request, ac_id):
|
||||
# عدم موفقیت: تنظیم nextNazarDT برای ۳ روز بعد (nazarDT پر نمیشود تا نهایی نشود)
|
||||
action.nazar = csDesc
|
||||
action.grade = int(csGrade)
|
||||
action.nextNazarDT = datetime.datetime.now() + datetime.timedelta(days=3)
|
||||
action.nextNazarDT = timezone.now() + datetime.timedelta(days=3)
|
||||
|
||||
elif csGrade == '-2':
|
||||
# کنسل کردن: پر کردن nazarDT برای خروج کامل از لیست کارهای در انتظار
|
||||
action.nazar = csDesc
|
||||
action.grade = int(csGrade)
|
||||
action.nazarDT = datetime.datetime.now()
|
||||
action.nazarDT = timezone.now()
|
||||
action.nextNazarDT = None
|
||||
|
||||
else:
|
||||
# ثبت امتیاز عادی
|
||||
action.nazar = csDesc
|
||||
action.grade = int(csGrade)
|
||||
action.nazarDT = datetime.datetime.now()
|
||||
action.nazarDT = timezone.now()
|
||||
action.nextNazarDT = None
|
||||
|
||||
action.save()
|
||||
|
||||
@@ -5,6 +5,7 @@ from django.db import models
|
||||
from django.db.models import Sum, Q
|
||||
from django.shortcuts import redirect, render, get_object_or_404
|
||||
from django.core.paginator import Paginator
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
@@ -17,6 +18,7 @@ from ..sms.phones import (normalize_primary_values,
|
||||
save_primary_normalized)
|
||||
from ..sms.triggers import notify_payment_thanks, notify_welcome
|
||||
from ..sms.rules import customer_toggle_map
|
||||
from ..visit_policy import (last_visit_map, next_appointment_map, visit_due_at)
|
||||
from ._helpers import logger, _parse_jalali_date, _parse_phone_rows
|
||||
|
||||
|
||||
@@ -327,56 +329,55 @@ def customerEdit(request, c_id):
|
||||
|
||||
@login_required(login_url='/panel/p_login/')
|
||||
def entezar(request):
|
||||
a = datetime.datetime.now()
|
||||
"""فهرست «منتظر بازدید».
|
||||
|
||||
منبع حقیقت تاریخ بازدید، ``visit_policy.last_visit_map`` است (نه ستون کششدهٔ
|
||||
``last_bazdid_dt``) تا این صفحه و اسکنر پیامک هرگز واگرا نشوند. دورهٔ بازدید هم
|
||||
از همان قالب پیامک خوانده میشود. مشتری دارای «تعویق فعال» (اقدام هماهنگی
|
||||
در انتظار با تاریخ آینده) از فهرست خارج و به صف خدمات پس از فروش سپرده میشود.
|
||||
"""
|
||||
searchVal = request.GET.get('searchVal', '')
|
||||
searchOn = request.GET.get('searchOn', '')
|
||||
searchStatus = True
|
||||
full_path = request.get_full_path()
|
||||
|
||||
from django.db.models import Q
|
||||
now = timezone.now()
|
||||
due_cut = visit_due_at(now=now)
|
||||
# مرز تاریخ به وقت محلی (نه UTC) تا صفحه و اسکنر پیامک یک جواب بدهند.
|
||||
due_cut_date = due_cut.astimezone(timezone.get_current_timezone()).date()
|
||||
|
||||
bazdid = os.environ.get('bazdid', 9)
|
||||
|
||||
bazdid = int ( int(bazdid) * 4.3 )
|
||||
|
||||
dateTime6MonthAgo = a - datetime.timedelta(weeks=bazdid)
|
||||
''' customers = Customer.objects.filter(name_family__isnull=False)
|
||||
m6mbazdid = []
|
||||
for customerObj in customers:
|
||||
try:
|
||||
actionAa = Action.objects.filter(customer=customerObj, actionType='bazdid', status='finish').last()
|
||||
if actionAa.status == 'finish':
|
||||
customerObj.last_bazdid_dt = actionAa.actionDateTime
|
||||
customerObj.save()
|
||||
if actionAa.actionDateTime.date() < dateTime6MonthAgo.date():
|
||||
m6mbazdid.append(actionAa)
|
||||
except:
|
||||
pass '''
|
||||
visits = last_visit_map(now=now)
|
||||
appointments = next_appointment_map(now=now)
|
||||
|
||||
customers_objj = Customer.objects.filter(
|
||||
laghv_bazdid=False, name_family__isnull=False,
|
||||
installDate__lte=due_cut_date,
|
||||
)
|
||||
if searchOn == 'address':
|
||||
print("addressaddressaddressaddressaddress")
|
||||
customers_objj = Customer.objects.filter(Q(last_bazdid_dt__lte=dateTime6MonthAgo) | Q(last_bazdid_dt__isnull=True),
|
||||
installDate__lte=dateTime6MonthAgo,
|
||||
laghv_bazdid=False, name_family__isnull=False, address__contains=searchVal).order_by('last_bazdid_dt')
|
||||
customers_objj = customers_objj.filter(address__contains=searchVal)
|
||||
else:
|
||||
searchStatus = False
|
||||
customers_objj = Customer.objects.filter(Q(last_bazdid_dt__lte=dateTime6MonthAgo) | Q(last_bazdid_dt__isnull=True),
|
||||
installDate__lte=dateTime6MonthAgo,
|
||||
laghv_bazdid=False, name_family__isnull=False).order_by('last_bazdid_dt')
|
||||
|
||||
#customers_objj = Customer.objects.filter(name_family__isnull=False, last_bazdid_dt__lte=dateTime6MonthAgo)
|
||||
# مشتریان عقبافتاده: بدون بازدید معتبر، یا بازدید معتبرشان پیش از مقطع دوره.
|
||||
stale_ids = [
|
||||
customer_id for customer_id in customers_objj.values_list('id', flat=True)
|
||||
if visits.get(customer_id) is None or visits[customer_id] <= due_cut
|
||||
]
|
||||
customers_objj = customers_objj.filter(id__in=stale_ids)
|
||||
|
||||
# «قرار بعدی» (هماهنگی توافقشده یا بازدید برنامهریزیشده) یعنی این مشتری
|
||||
# در این فهرست جایی ندارد تا آن تاریخ.
|
||||
pending_ids = [cid for cid in stale_ids if cid in appointments]
|
||||
if pending_ids:
|
||||
customers_objj = customers_objj.exclude(id__in=pending_ids)
|
||||
|
||||
customers_objj = customers_objj.order_by('last_bazdid_dt', 'id')
|
||||
|
||||
try:
|
||||
entezar_bazdid = CacheTable.objects.get(key='9mahbazdid')
|
||||
|
||||
entezar_bazdid.value = Customer.objects.filter(
|
||||
Q(last_bazdid_dt__lte=dateTime6MonthAgo) | Q(last_bazdid_dt__isnull=True),
|
||||
installDate__lte=dateTime6MonthAgo,
|
||||
laghv_bazdid=False, name_family__isnull=False).count()
|
||||
|
||||
entezar_bazdid.value = len(stale_ids) - len(pending_ids)
|
||||
entezar_bazdid.save()
|
||||
except:
|
||||
except CacheTable.DoesNotExist:
|
||||
pass
|
||||
|
||||
paginator = Paginator(customers_objj, 20) # Show 25 contacts per page.
|
||||
|
||||
@@ -597,6 +597,10 @@ def sms_queue_delete(request, q_id):
|
||||
if request.method == "POST":
|
||||
if item.status in DONE_STATUSES:
|
||||
return _notice(reverse("admin_panel:sms_queue"), "delete_done_blocked")
|
||||
# تصمیم محصولی: حذف توسط مدیر = «کنسل همین دوره». برای همین کلید یکتاسازی
|
||||
# پیش از حذف در دفتر ثبت میشود؛ وگرنه اسکنر در اجرای بعدی همان پیامک را
|
||||
# دوباره میساخت.
|
||||
_remember_keys([item])
|
||||
item.delete()
|
||||
return _notice(reverse("admin_panel:sms_queue"), "deleted")
|
||||
return redirect("admin_panel:sms_queue")
|
||||
@@ -621,6 +625,7 @@ def sms_queue_bulk(request):
|
||||
# تاریخچهٔ ارسالشدهها از طریق صف حذف نمیشود.
|
||||
selected = selected.exclude(status__in=DONE_STATUSES)
|
||||
count = selected.count()
|
||||
_remember_keys(selected)
|
||||
selected.delete()
|
||||
code, extra = "bulk_deleted", {"count": count}
|
||||
else:
|
||||
@@ -629,6 +634,19 @@ def sms_queue_bulk(request):
|
||||
return redirect("admin_panel:sms_queue")
|
||||
|
||||
|
||||
def _remember_keys(rows):
|
||||
"""کلیدهای یکتاسازیِ رکوردهای صف را پیش از حذف در دفتر ``SMSDedup`` ثبت میکند.
|
||||
|
||||
قرارداد: وقتی مدیر یادآوری را حذف میکند، یعنی «همین دوره کنسل شد»؛ وگرنه
|
||||
اسکنر در اجرای بعدی همان پیامک را از نو میسازد و حذفِ مدیر بیاثر میشود.
|
||||
"""
|
||||
keys = [k for k in (row.dedup_key for row in rows) if k]
|
||||
if keys:
|
||||
SMSDedup.objects.bulk_create(
|
||||
[SMSDedup(key=key) for key in keys], ignore_conflicts=True,
|
||||
)
|
||||
|
||||
|
||||
@login_required(login_url='/panel/p_login/')
|
||||
@_manage_required
|
||||
def sms_queue_empty(request):
|
||||
@@ -640,10 +658,7 @@ def sms_queue_empty(request):
|
||||
"""
|
||||
if request.method == "POST":
|
||||
rows = SMSQueue.objects.exclude(status__in=DONE_STATUSES)
|
||||
keys = [k for k in rows.values_list("dedup_key", flat=True) if k]
|
||||
SMSDedup.objects.bulk_create(
|
||||
[SMSDedup(key=k) for k in keys], ignore_conflicts=True,
|
||||
)
|
||||
_remember_keys(rows)
|
||||
count = rows.count()
|
||||
rows.delete()
|
||||
return _notice(reverse("admin_panel:sms_queue"), "queue_emptied", count=count)
|
||||
|
||||
Reference in new issue
Block a user