hesabdari va dashboard

This commit is contained in:
javad committed 2026-06-30 20:25:39 +03:30
1 parent 767e84cbcf
commit bd775d6249
5 files changed
+808 -244

No files matched your search

+196 -10
View File
@@ -266,6 +266,159 @@ def inventory_movements_report(request):
return render(request, 'admin_panel/report_inventory_movements.html', context)
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def financial_dashboard(request):
"""
داشبورد هوش مالی و مدیریت نقدینگی سطح Enterprise (BI Dashboard)
همراه با روند ۱۲ ماهه جمع کل بدهی مشتریان (تفاضل debit و credit).
"""
# دریافت تایم‌استمپ‌های ارسالی از تقویم شمسی فرانت‌اند
az_tarikh = request.GET.get('azTarikh', '')
ta_tarikh = request.GET.get('taTarikh', '')
az_dt = None
ta_dt = None
if az_tarikh:
try:
az_dt = datetime.datetime.fromtimestamp(int(az_tarikh) / 1000.0)
az_dt = az_dt.replace(hour=0, minute=0, second=0)
except (ValueError, TypeError):
pass
if ta_tarikh:
try:
ta_dt = datetime.datetime.fromtimestamp(int(ta_tarikh) / 1000.0)
ta_dt = ta_dt.replace(hour=23, minute=59, second=59)
except (ValueError, TypeError):
pass
# نقطه اتکا (لنگر) محاسبات: زمان انتخابی کاربر یا زمان حال
anchor_dt = ta_dt if ta_dt else datetime.datetime.now()
# =========================================================================
# محاسبه روند ۱۲ ماهه غلتان (TTM) - نسخه اصلاح‌شده و امن
# =========================================================================
jalali_months = ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر', 'آبان', 'آذر', 'دی', 'بهمن', 'اسفند']
trend_labels = []
trend_data = []
# روش استاندارد و بدون باگ برای تبدیل تاریخ میلادی به شمسی در persiantools
# با این کار مطمئن می‌شویم مقادیر حتماً عدد صحیح و مجاز هستند
anchor_jd = JalaliDate(anchor_dt.date())
curr_y = int(anchor_jd.year)
curr_m = int(anchor_jd.month)
# حلقه استخراج داده برای ۱۲ ماه گذشته
for _ in range(12):
if curr_m < 1:
curr_m = 12
curr_y -= 1
label = f"{jalali_months[curr_m - 1]} {curr_y}"
trend_labels.insert(0, label)
# ❗️ اصلاح اساسی: ترتیب آرگومان‌ها در این تابع باید (month, year) باشد
last_day = JalaliDate.days_in_month(curr_m, curr_y)
# اما ترتیب سازنده کلاس اصلی همچنان (year, month, day) است
end_of_month_jd = JalaliDate(curr_y, curr_m, last_day)
end_of_month_gd = end_of_month_jd.to_gregorian()
# ساخت رشته زمان برای پایان آن ماه (تا ساعت 23:59:59)
end_dt_str = end_of_month_gd.strftime('%Y-%m-%d 23:59:59')
# محاسبه مانده حساب کل تا پایان این ماه
totals = Article.objects.filter(createdDT__lte=end_dt_str).aggregate(
t_deb=Sum('debit'),
t_cred=Sum('credit')
)
t_deb = totals['t_deb'] or 0
t_cred = totals['t_cred'] or 0
month_total_debt = t_deb - t_cred
trend_data.insert(0, month_total_debt)
# شیفت به ماه قبل برای دور بعدی حلقه
curr_m -= 1
# =========================================================================
# قرنطینه داده‌های سایر بخش‌های داشبورد بر اساس سال مالی ۱۴۰۰
# =========================================================================
sarresid_qs = Sarresid.objects.filter(factor__productinstore__year=1400).distinct()
factor_qs = Factor.objects.filter(productinstore__year=1400, is_active=True).distinct()
article_qs = Article.objects.filter(factor__productinstore__year=1400).distinct()
store_qs = ProductInStore.objects.filter(year=1400)
if az_dt:
sarresid_qs = sarresid_qs.filter(createdDT__gte=az_dt)
factor_qs = factor_qs.filter(create_date__gte=az_dt)
article_qs = article_qs.filter(createdDT__gte=az_dt)
store_qs = store_qs.filter(createdDT__gte=az_dt)
if ta_dt:
sarresid_qs = sarresid_qs.filter(createdDT__lte=ta_dt)
factor_qs = factor_qs.filter(create_date__lte=ta_dt)
article_qs = article_qs.filter(createdDT__lte=ta_dt)
store_qs = store_qs.filter(createdDT__lte=ta_dt)
# ۱. محاسبات کارت‌های شاخص عملکرد (KPI Cards)
total_unpaid_debts = sarresid_qs.filter(payStatus="false").aggregate(Sum('price'))['price__sum'] or 0
total_sales_period = factor_qs.aggregate(Sum('amount'))['amount__sum'] or 0
overdue_checks_count = sarresid_qs.filter(payStatus="false", type="check", sarresidDT__lte=anchor_dt).count()
# ۲. نمودار اول: تحلیل پویای سن مطالبات
debt_future = sarresid_qs.filter(payStatus="false", sarresidDT__gt=anchor_dt).aggregate(Sum('price'))['price__sum'] or 0
dt_30_days_ago = anchor_dt - datetime.timedelta(days=30)
debt_0_30 = sarresid_qs.filter(payStatus="false", sarresidDT__lte=anchor_dt, sarresidDT__gte=dt_30_days_ago).aggregate(Sum('price'))['price__sum'] or 0
dt_90_days_ago = anchor_dt - datetime.timedelta(days=90)
debt_30_90 = sarresid_qs.filter(payStatus="false", sarresidDT__lt=dt_30_days_ago, sarresidDT__gte=dt_90_days_ago).aggregate(Sum('price'))['price__sum'] or 0
debt_above_90 = sarresid_qs.filter(payStatus="false", sarresidDT__lt=dt_90_days_ago).aggregate(Sum('price'))['price__sum'] or 0
# ۳. نمودار دوم: ماتریس کیفیت ورودی پول
payment_articles = article_qs.filter(credit__gt=0).values('nahvePardakht').annotate(total_amount=Sum('credit')).order_by('-total_amount')
pay_labels = []
pay_data = []
for item in payment_articles:
label = item['nahvePardakht'] or 'سایر/نامشخص'
pay_labels.append(label)
pay_data.append(item['total_amount'])
# ۴. نمودار سوم: مانیتورینگ نشت انبار
store_leaks = store_qs.filter(
movement_type__in=[ProductInStore.MovementType.WARRANTY, ProductInStore.MovementType.PROMOTION]
).values('movement_type').annotate(total_qty=Sum('s_out'))
leak_data = {'WARRANTY': 0, 'PROMOTION': 0}
for leak in store_leaks:
leak_data[leak['movement_type']] = leak['total_qty']
# ارسال ساختار داده به تمپلیت
context = {
'kpi_total_unpaid': total_unpaid_debts,
'kpi_total_sales': total_sales_period,
'kpi_overdue_checks': overdue_checks_count,
'trend_labels': trend_labels, # نام ماه‌ها برای محور X
'trend_data': trend_data, # جمع کل بدهی‌ها برای محور Y
'aging_data': [debt_future, debt_0_30, debt_30_90, debt_above_90],
'pay_labels': pay_labels,
'pay_data': pay_data,
'warranty_qty': leak_data['WARRANTY'],
'promotion_qty': leak_data['PROMOTION'],
'filters': {
'azTarikh': az_tarikh,
'taTarikh': ta_tarikh,
}
}
return render(request, 'admin_panel/financial_dashboard.html', context)
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def bedehi(request):
@@ -288,25 +441,58 @@ def bedehi(request):
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def hesabdari(request):
page_name = 'sarresidShodeha'
anchor_dt = datetime.datetime.now()
dateNow = datetime.datetime.today().strftime('%Y-%m-%d 23:59:59')
all_sarresid = Sarresid.objects.filter(payStatus="false", sarresidDT__lte=dateNow).order_by('-sarresidDT')
# تعریف محدوده‌های زمانی ماتریس ریسک (سن مطالبات)
dt_30_days_ago = anchor_dt - datetime.timedelta(days=30)
dt_90_days_ago = anchor_dt - datetime.timedelta(days=90)
sum_sarresid = all_sarresid.aggregate(Sum('price'))['price__sum']
# کوئری پایه: تمام سررسیدهای پرداخت‌نشده که از موعد آن‌ها گذشته است
base_qs = Sarresid.objects.filter(payStatus="false", sarresidDT__lte=anchor_dt)
page_number = request.GET.get('page', None)
paginator = Paginator(all_sarresid, per_page=50)
# محاسبه پرسرعت مجموع مبالغ در هر دسته (توسط موتور دیتابیس)
matrix_totals = base_qs.aggregate(
minor_delay=Sum('price', filter=Q(sarresidDT__gte=dt_30_days_ago)),
high_risk=Sum('price', filter=Q(sarresidDT__lt=dt_30_days_ago, sarresidDT__gte=dt_90_days_ago)),
critical=Sum('price', filter=Q(sarresidDT__lt=dt_90_days_ago)),
total_sum=Sum('price')
)
# دریافت فیلتر انتخابی کاربر از URL
risk_filter = request.GET.get('risk_level', 'all')
# اعمال فیلتر روی لیست نمایش داده شده
if risk_filter == 'minor':
filtered_qs = base_qs.filter(sarresidDT__gte=dt_30_days_ago)
elif risk_filter == 'high':
filtered_qs = base_qs.filter(sarresidDT__lt=dt_30_days_ago, sarresidDT__gte=dt_90_days_ago)
elif risk_filter == 'critical':
filtered_qs = base_qs.filter(sarresidDT__lt=dt_90_days_ago)
else:
filtered_qs = base_qs
# مرتب‌سازی: قدیمی‌ترین‌ها (خطرناک‌ترین‌ها) در صدر جدول قرار می‌گیرند
filtered_qs = filtered_qs.order_by('sarresidDT')
page_number = request.GET.get('page', 1)
paginator = Paginator(filtered_qs, per_page=50)
all_sarresid_pgi = paginator.get_page(page_number)
#paginator = Paginator(all_sarresid, per_page=200)
data = {'sum_sarresid': sum_sarresid,'all_sarresid': all_sarresid_pgi, "dateNow":dateNow, "page_name": page_name}
data = {
'page_name': page_name,
'dateNow': anchor_dt.strftime('%Y-%m-%d %H:%M:%S'),
'all_sarresid': all_sarresid_pgi,
'current_filter': risk_filter,
# متغیرهای ماتریس ریسک
'sum_sarresid': matrix_totals['total_sum'] or 0,
'minor_sum': matrix_totals['minor_delay'] or 0,
'high_sum': matrix_totals['high_risk'] or 0,
'critical_sum': matrix_totals['critical'] or 0,
}
return render(request, 'admin_panel/hesabdari.html', data)
@login_required(login_url='/panel/p_login/')
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
def hesabdari_checkPass(request):