hesabresi and desc transfer
This commit is contained in:
1 parent
e353ed6fc2
commit
16eb747b5c
7 files changed
+678
-78
No files matched your search
+73
-27
@@ -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
|
||||
from django.db.models import Count, Sum, Q, Avg, F, Subquery, OuterRef, IntegerField
|
||||
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
|
||||
@@ -282,10 +282,9 @@ def inventory_movements_report(request):
|
||||
@permission_required('admin_panel.ViewReportMali', raise_exception=True)
|
||||
def financial_dashboard(request):
|
||||
"""
|
||||
داشبورد هوش مالی و مدیریت نقدینگی سطح Enterprise (BI Dashboard)
|
||||
همراه با روند ۱۲ ماهه جمع کل بدهی مشتریان (تفاضل debit و credit).
|
||||
داشبورد هوش مالی و مدیریت نقدینگی (BI Dashboard)
|
||||
مجهز به هسته حسابرسی ۳۶۰ درجه برای کشف تمامی مغایرتها (بدهی رو هوا، چکهای الکی، پیشدریافتها)
|
||||
"""
|
||||
# دریافت تایماستمپهای ارسالی از تقویم شمسی فرانتاند
|
||||
az_tarikh = request.GET.get('azTarikh', '')
|
||||
ta_tarikh = request.GET.get('taTarikh', '')
|
||||
|
||||
@@ -306,23 +305,19 @@ def financial_dashboard(request):
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# نقطه اتکا (لنگر) محاسبات: زمان انتخابی کاربر یا زمان حال
|
||||
anchor_dt = ta_dt if ta_dt else datetime.datetime.now()
|
||||
|
||||
# =========================================================================
|
||||
# محاسبه روند ۱۲ ماهه غلتان (TTM) - نسخه اصلاحشده و امن
|
||||
# محاسبه روند ۱۲ ماهه غلتان (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
|
||||
@@ -331,17 +326,11 @@ def financial_dashboard(request):
|
||||
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')
|
||||
@@ -351,14 +340,11 @@ def financial_dashboard(request):
|
||||
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()
|
||||
@@ -376,12 +362,65 @@ def financial_dashboard(request):
|
||||
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()
|
||||
|
||||
# ۲. نمودار اول: تحلیل پویای سن مطالبات
|
||||
# =========================================================================
|
||||
# 👑 هسته حسابرسی ۳۶۰ درجه (برگرفته از Ghost Hunter / Deep Audit)
|
||||
# =========================================================================
|
||||
|
||||
# الف) تراز کل ریاضی سیستم
|
||||
g_art = Article.objects.aggregate(d=Sum('debit'), c=Sum('credit'))
|
||||
global_mande = (g_art['d'] or 0) - (g_art['c'] or 0)
|
||||
g_sar = Sarresid.objects.filter(payStatus="false").aggregate(s=Sum('price'))['s'] or 0
|
||||
global_diff = global_mande - g_sar
|
||||
|
||||
# ب) محاسبات 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_sarresid = Sarresid.objects.filter(customer=OuterRef('pk'), payStatus="false").values('customer').annotate(s=Sum('price')).values('s')
|
||||
|
||||
base_customers = Customer.objects.filter(is_active=True).annotate(
|
||||
t_debit=Coalesce(Subquery(sq_debit, output_field=IntegerField()), 0),
|
||||
t_credit=Coalesce(Subquery(sq_credit, output_field=IntegerField()), 0),
|
||||
t_sarresid=Coalesce(Subquery(sq_sarresid, output_field=IntegerField()), 0),
|
||||
).annotate(
|
||||
net_debt=F('t_debit') - F('t_credit') # تراز نهایی
|
||||
).annotate(
|
||||
diff=F('net_debt') - F('t_sarresid') # میزان کسر/اضافه سند
|
||||
)
|
||||
|
||||
# 🔴 گروه 1: بدهکارانی که کسر سند/چک دارند (بدهی خطرناک روی هوا) [diff > 0]
|
||||
group1_qs = base_customers.filter(diff__gt=0).order_by('-diff')
|
||||
group1_count = group1_qs.count()
|
||||
group1_total = sum(group1_qs.values_list('diff', flat=True)) or 0
|
||||
group1_sample = group1_qs[:20]
|
||||
|
||||
# 🟡 گروه 2: چک پاسنشده در سیستم بیشتر از بدهی است (خطای ثبتی اپراتور) [diff < 0 AND net_debt >= 0]
|
||||
group2_qs = base_customers.filter(diff__lt=0, net_debt__gte=0).annotate(
|
||||
fake_amount=F('t_sarresid') - F('net_debt')
|
||||
).order_by('-fake_amount')
|
||||
group2_count = group2_qs.count()
|
||||
group2_total = sum(group2_qs.values_list('fake_amount', flat=True)) or 0
|
||||
group2_sample = group2_qs[:20]
|
||||
|
||||
# 🟢 گروه 3: پیشدریافت خالص (بستانکار است و هیچ چکی ندارد) [diff < 0 AND net_debt < 0 AND t_sarresid = 0]
|
||||
group3_qs = base_customers.filter(diff__lt=0, net_debt__lt=0, t_sarresid=0).order_by('net_debt') # net_debt منفی است
|
||||
group3_count = group3_qs.count()
|
||||
group3_total = sum(group3_qs.values_list('net_debt', flat=True)) or 0
|
||||
group3_sample = group3_qs[:20]
|
||||
|
||||
# 🟣 گروه 4: عجیبترین حالت! مشتری بستانکار است، اما چک پاسنشده هم در سیستم دارد! [diff < 0 AND net_debt < 0 AND t_sarresid > 0]
|
||||
group4_qs = base_customers.filter(diff__lt=0, net_debt__lt=0, t_sarresid__gt=0).order_by('net_debt')
|
||||
group4_count = group4_qs.count()
|
||||
group4_total = sum(group4_qs.values_list('diff', flat=True)) or 0
|
||||
group4_sample = group4_qs[:20]
|
||||
|
||||
# =========================================================================
|
||||
# نمودارها و آمار تکمیلی داشبورد
|
||||
# =========================================================================
|
||||
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
|
||||
@@ -389,7 +428,6 @@ def financial_dashboard(request):
|
||||
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 = []
|
||||
@@ -398,7 +436,6 @@ def financial_dashboard(request):
|
||||
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'))
|
||||
@@ -407,14 +444,24 @@ def financial_dashboard(request):
|
||||
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
|
||||
# آمار کلان ریاضیاتی
|
||||
'global_mande': global_mande,
|
||||
'g_sar': g_sar,
|
||||
'global_diff': global_diff,
|
||||
|
||||
# متغیرهای ۴ گروه حسابرسی
|
||||
'group1_count': group1_count, 'group1_total': group1_total, 'group1_sample': group1_sample,
|
||||
'group2_count': group2_count, 'group2_total': group2_total, 'group2_sample': group2_sample,
|
||||
'group3_count': group3_count, 'group3_total': group3_total, 'group3_sample': group3_sample,
|
||||
'group4_count': group4_count, 'group4_total': group4_total, 'group4_sample': group4_sample,
|
||||
|
||||
'trend_labels': trend_labels,
|
||||
'trend_data': trend_data,
|
||||
|
||||
'aging_data': [debt_future, debt_0_30, debt_30_90, debt_above_90],
|
||||
'pay_labels': pay_labels,
|
||||
@@ -430,7 +477,6 @@ def financial_dashboard(request):
|
||||
|
||||
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):
|
||||
|
||||
Reference in new issue
Block a user