hesabdari va dashboard
This commit is contained in:
1 parent
767e84cbcf
commit
bd775d6249
5 files changed
+808
-244
No files matched your search
+196
-10
@@ -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):
|
||||
|
||||
Reference in new issue
Block a user