gozaresh vosol motalebat

This commit is contained in:
javad committed 2026-08-21 13:17:59 +03:30
1 parent 0d867367f9
commit 99a86c7073
4 files changed
+404 -1

No files matched your search

+112 -1
View File
@@ -22,7 +22,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, Avg, F, Subquery, OuterRef, IntegerField
from django.db.models import Count, Sum, Q, Avg, F, Subquery, OuterRef, IntegerField, Exists, Min, Max, Case, When
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
@@ -196,6 +196,117 @@ def report(request):
data = {'nahvePardakhat': nahvePardakhat, 'userHa':userHa}
return render(request, 'admin_panel/report.html', data)
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def report_debtors_no_vosol(request):
now = datetime.datetime.now()
# --- ۱. دریافت و اعتبارسنجی پارامترهای فیلتر از فرانت‌اند ---
try:
days_threshold = int(request.GET.get('days', 20))
if days_threshold < 0:
days_threshold = 20
except ValueError:
days_threshold = 20
filter_type = request.GET.get('filter_type', 'all') # all, never, expired
sort_field = request.GET.get('sort', 'amount') # amount, oldest_date, last_action
sort_dir = request.GET.get('dir', 'desc') # asc, desc
threshold_date = now - datetime.timedelta(days=days_threshold)
# --- ۲. ساخت Subquery های فوق‌سریع و ایمن ---
sq_debit = Article.objects.filter(customer=OuterRef('pk')).values('customer').annotate(s=Sum('debit')).values('s')
sq_credit = Article.objects.filter(customer=OuterRef('pk')).values('customer').annotate(s=Sum('credit')).values('s')
sq_overdue_amt = Sarresid.objects.filter(
customer=OuterRef('pk'), payStatus='false', sarresidDT__lte=now
).values('customer').annotate(s=Sum('price')).values('s')
sq_oldest_date = Sarresid.objects.filter(
customer=OuterRef('pk'), payStatus='false', sarresidDT__lte=now
).values('customer').annotate(min_dt=Min('sarresidDT')).values('min_dt')
sq_overdue_count = Sarresid.objects.filter(
customer=OuterRef('pk'), payStatus='false', sarresidDT__lte=now
).values('customer').annotate(c=Count('id')).values('c')
# استخراج آخرین سابقه اکشن وصول
latest_vosol_qs = Action.objects.filter(
customer=OuterRef('pk'), actionType='vosol'
).order_by('-createdDT')
# --- ۳. ساخت کوئری پایه (فقط مشتریان فعال و دارای معوقه) ---
base_qs = Customer.objects.filter(is_active=True).annotate(
t_debit=Coalesce(Subquery(sq_debit, output_field=IntegerField()), Value(0)),
t_credit=Coalesce(Subquery(sq_credit, output_field=IntegerField()), Value(0)),
overdue_amount=Coalesce(Subquery(sq_overdue_amt, output_field=IntegerField()), Value(0)),
overdue_count=Coalesce(Subquery(sq_overdue_count, output_field=IntegerField()), Value(0)),
oldest_overdue_date=Subquery(sq_oldest_date),
last_vosol_date=Subquery(latest_vosol_qs.values('createdDT')[:1]),
last_vosol_desc=Subquery(latest_vosol_qs.values('desc')[:1])
).annotate(
net_debt=F('t_debit') - F('t_credit')
).filter(
overdue_amount__gt=0 # شرط اساسی: فقط کسانی که معوقه سررسید گذشته دارند
)
# استخراج مشتریانی که نیازمند پیگیری هستند (هرگز پیگیری نشده‌اند یا موعد پیگیری‌شان گذشته است)
action_needed_qs = base_qs.filter(
Q(last_vosol_date__isnull=True) | Q(last_vosol_date__lt=threshold_date)
)
# --- ۴. محاسبه آمار دقیق (بدون خطا) برای داشبورد ---
# استفاده از Case/When برای جلوگیری کامل از خطای None در Aggregate
stats = action_needed_qs.aggregate(
never_count=Coalesce(Sum(Case(When(last_vosol_date__isnull=True, then=1), default=Value(0), output_field=IntegerField())), Value(0)),
never_amount=Coalesce(Sum(Case(When(last_vosol_date__isnull=True, then=F('overdue_amount')), default=Value(0), output_field=IntegerField())), Value(0)),
expired_count=Coalesce(Sum(Case(When(last_vosol_date__isnull=False, then=1), default=Value(0), output_field=IntegerField())), Value(0)),
expired_amount=Coalesce(Sum(Case(When(last_vosol_date__isnull=False, then=F('overdue_amount')), default=Value(0), output_field=IntegerField())), Value(0)),
)
# --- ۵. اعمال فیلتر کشویی (Dropdown) ---
if filter_type == 'never':
final_qs = action_needed_qs.filter(last_vosol_date__isnull=True)
elif filter_type == 'expired':
final_qs = action_needed_qs.filter(last_vosol_date__isnull=False)
else: # all
final_qs = action_needed_qs
# --- ۶. اعمال مرتب‌سازی (Sorting) پویا ---
if sort_field == 'amount':
order_expr = F('overdue_amount').desc() if sort_dir == 'desc' else F('overdue_amount').asc()
elif sort_field == 'oldest_date':
order_expr = F('oldest_overdue_date').desc(nulls_last=True) if sort_dir == 'desc' else F('oldest_overdue_date').asc(nulls_last=True)
elif sort_field == 'last_action':
order_expr = F('last_vosol_date').desc(nulls_last=True) if sort_dir == 'desc' else F('last_vosol_date').asc(nulls_first=True)
else:
order_expr = F('overdue_amount').desc()
final_qs = final_qs.order_by(order_expr)
# --- ۷. صفحه‌بندی ایمن ---
paginator = Paginator(final_qs, 50)
page_number = request.GET.get('page', 1)
try:
page_obj = paginator.page(page_number)
except (PageNotAnInteger, EmptyPage):
page_obj = paginator.page(1)
context = {
'page_obj': page_obj,
'page_name': 'debtors_no_vosol',
'stats': stats,
# پارامترهای کنترل پنل
'current_days': days_threshold,
'current_filter': filter_type,
'current_sort': sort_field,
'current_dir': sort_dir,
}
return render(request, 'admin_panel/report_debtors_no_vosol.html', context)
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def inventory_movements_report(request):