hesabresi and desc transfer
This commit is contained in:
1 parent
e353ed6fc2
commit
16eb747b5c
7 files changed
+678
-78
No files matched your search
@@ -127,7 +127,7 @@ def approve_transfer_request(*, request_id: int, approver: AbstractBaseUser) ->
|
||||
s_in=0,
|
||||
s_out=transfer.quantity,
|
||||
movement_type=ProductInStore.MovementType.TRANSFER,
|
||||
desc=f"انتقال به {transfer.target_store.store_name} | درخواست #{transfer.id} | تایید توسط {approver.username}"
|
||||
desc=f"انتقال به {transfer.target_store.store_name} | درخواست #{transfer.id} | تایید توسط {approver.username} | {transfer.desc}"
|
||||
)
|
||||
|
||||
# ۵. صدور سند ورود قطعی به انبار مقصد
|
||||
@@ -137,7 +137,7 @@ def approve_transfer_request(*, request_id: int, approver: AbstractBaseUser) ->
|
||||
s_in=transfer.quantity,
|
||||
s_out=0,
|
||||
movement_type=ProductInStore.MovementType.TRANSFER,
|
||||
desc=f"انتقال از {transfer.source_store.store_name} | درخواست #{transfer.id} | تایید توسط {approver.username}"
|
||||
desc=f"انتقال از {transfer.source_store.store_name} | درخواست #{transfer.id} | تایید توسط {approver.username} | {transfer.desc}"
|
||||
)
|
||||
|
||||
# ۶. بهروزرسانی نهایی درخواست
|
||||
|
||||
@@ -38,38 +38,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12-mobile is-4-tablet">
|
||||
<div class="box p-4 has-background-white shadow-card border-left-danger h-100">
|
||||
<div class="level is-mobile m-0">
|
||||
<div class="level-left">
|
||||
<div>
|
||||
<p class="heading has-text-grey-dark is-size-7 mb-1">کل مطالبات معوق بازار (سرمایه قفل شده)</p>
|
||||
<p class="title is-size-4 is-size-5-mobile toCurrency has-text-danger-dark">{{ kpi_total_unpaid }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- هشدار هوشمند خطاهای سیستمی -->
|
||||
{% if group1_count > 0 or group2_count > 0 or group4_count > 0 %}
|
||||
<div class="notification is-danger is-light mb-4 py-3 shadow-sm is-flex is-align-items-center is-justify-content-space-between is-flex-wrap-wrap">
|
||||
<div class="mb-2-mobile">
|
||||
<span class="icon has-text-danger"><i class="fas fa-exclamation-circle"></i></span>
|
||||
<strong class="has-text-danger-dark">حسابرسی خودکار سیستم:</strong>
|
||||
تعداد <b>{{ group1_count }} مورد</b> بدهی پرخطر، <b>{{ group2_count }} مورد</b> خطای ثبتی چک و <b>{{ group4_count }} مورد</b> رکورد نامتعارف یافت شد!
|
||||
</div>
|
||||
<a href="#audit-section" class="button is-danger is-small is-rounded is-outlined px-4">مشاهده گزارش حسابرسی (پایین صفحه)</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- بخش اول: کارتهای شاخص (KPI) -->
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-12-mobile is-4-tablet">
|
||||
<div class="box p-4 has-background-white shadow-card border-left-success h-100">
|
||||
<div class="level is-mobile m-0">
|
||||
<div class="level-left">
|
||||
<div>
|
||||
<p class="heading has-text-grey-dark is-size-7 mb-1">حجم ناخالص فروش در بازه انتخابی</p>
|
||||
<p class="title is-size-4 is-size-5-mobile toCurrency has-text-success-dark">{{ kpi_total_sales }}</p>
|
||||
<p class="heading has-text-grey-dark is-size-7 mb-1" title="مجموع مبلغ اسناد در دست (چکها و سررسیدهای پرداخت نشده)">مطالبات دارای سند (چک/سررسید)</p>
|
||||
<p class="title is-size-4 is-size-5-mobile toCurrency has-text-success-dark">{{ kpi_total_unpaid }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-4-tablet">
|
||||
<div class="box p-4 has-background-white shadow-card border-left-warning h-100">
|
||||
<div class="box p-4 has-background-white shadow-card border-left-info h-100">
|
||||
<div class="level is-mobile m-0">
|
||||
<div class="level-left">
|
||||
<div>
|
||||
<p class="heading has-text-grey-dark is-size-7 mb-1">تعداد چکهای سررسیدگذشته و برگشتی</p>
|
||||
<p class="title is-size-4 is-size-5-mobile has-text-warning-dark">{{ kpi_overdue_checks }} <span class="is-size-6">فقره</span></p>
|
||||
<p class="heading has-text-grey-dark is-size-7 mb-1">حجم ناخالص فروش در بازه انتخابی</p>
|
||||
<p class="title is-size-4 is-size-5-mobile toCurrency has-text-info-dark">{{ kpi_total_sales }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-12-mobile is-4-tablet">
|
||||
<div class="box p-4 has-background-danger-light shadow-card border-left-danger h-100" title="بدهی مشتریانی که هیچگونه چک یا سررسیدی برای بدهی خود ارائه نکردهاند">
|
||||
<div class="level is-mobile m-0">
|
||||
<div class="level-left">
|
||||
<div>
|
||||
<p class="heading has-text-danger-dark is-size-7 mb-1 font-weight-bold">مجموع مطالبات پرخطر (بدون سند)</p>
|
||||
<p class="title is-size-4 is-size-5-mobile toCurrency has-text-danger-dark">{{ group1_total }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,6 +90,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- بخش دوم: نمودارها -->
|
||||
<div class="columns is-multiline mt-1">
|
||||
<div class="column is-12">
|
||||
<div class="box p-4 shadow-card">
|
||||
@@ -96,11 +110,10 @@
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline mt-1">
|
||||
|
||||
<div class="column is-12-mobile is-7-desktop">
|
||||
<div class="box p-4 shadow-card h-100">
|
||||
<p class="title is-6 is-size-7-mobile pb-2 mb-4" style="border-bottom: 1px solid #f0f0f0;">
|
||||
📊 ماتریس ریسک و تحلیل سنی مطالبات
|
||||
📊 ماتریس ریسک و تحلیل سنی اسناد (چک/نسیه)
|
||||
</p>
|
||||
<div class="chart-wrapper-tall">
|
||||
<canvas id="agingChart"></canvas>
|
||||
@@ -152,8 +165,266 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================================= -->
|
||||
<!-- بخش سوم: کالبدشکافی ۳۶۰ درجه و سیستم جامع حسابرسی -->
|
||||
<!-- ========================================================= -->
|
||||
<div id="audit-section" class="columns is-multiline mt-5 mb-4">
|
||||
|
||||
<div class="column is-12">
|
||||
<h3 class="title is-5 mb-2"><i class="fas fa-microscope has-text-primary mr-2"></i> سیستم حسابرسی و مغایرتگیری </h3>
|
||||
|
||||
<!-- خلاصه تراز کل -->
|
||||
<div class="box p-3 has-background-light shadow-sm" style="border: 1px solid #e0e0e0;">
|
||||
<div class="columns is-mobile is-multiline has-text-centered is-size-7">
|
||||
<div class="column is-4-tablet is-12-mobile">
|
||||
<span class="has-text-grey">مجموع تراز دفتر کل:</span> <br>
|
||||
<strong class="is-size-5 toCurrency">{{ global_mande }}</strong>
|
||||
</div>
|
||||
<div class="column is-4-tablet is-12-mobile" style="border-right: 1px solid #ddd; border-left: 1px solid #ddd;">
|
||||
<span class="has-text-grey">مجموع چکها/اسناد در سیستم:</span> <br>
|
||||
<strong class="is-size-5 toCurrency has-text-success-dark">{{ g_sar }}</strong>
|
||||
</div>
|
||||
<div class="column is-4-tablet is-12-mobile">
|
||||
<span class="has-text-grey">اختلاف ریاضی سیستم:</span> <br>
|
||||
<strong class="is-size-5 toCurrency has-text-danger-dark" dir="ltr">{{ global_diff }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 🔴 گروه اول: بدهکاران پرخطر -->
|
||||
<div class="column is-12">
|
||||
<div class="box p-4 shadow-card border-left-danger">
|
||||
<div class="level is-mobile is-flex-wrap-wrap mb-4 pb-2" style="border-bottom: 1px solid #fee2e2;">
|
||||
<div class="level-left mb-2-mobile">
|
||||
<p class="title is-6 is-size-7-mobile mb-0 has-text-danger-dark">
|
||||
<span class="icon is-small ml-1"><i class="fas fa-exclamation-triangle"></i></span>
|
||||
گروه ۱: مطالبات فاقد پوشش (کسری چک / بدهی خطرناک)
|
||||
</p>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<span class="tag is-danger is-light ml-2">مجموع کسر سند: <span class="toCurrency mr-1">{{ group1_total }}</span> تومان</span>
|
||||
<span class="tag is-danger">تعداد: {{ group1_count }} نفر</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if group1_sample %}
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable is-striped is-bordered is-size-7">
|
||||
<thead>
|
||||
<tr class="has-background-danger-light">
|
||||
<th class="has-text-centered">ردیف</th>
|
||||
<th>نام مشتری</th>
|
||||
<th>تراز دفتر کل (بدهی)</th>
|
||||
<th>اسناد موجود</th>
|
||||
<th class="has-text-danger-dark">مقدار کسر چک (بدهی روی هوا)</th>
|
||||
<th class="has-text-centered">عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in group1_sample %}
|
||||
<tr>
|
||||
<td class="has-text-centered">{{ forloop.counter }}</td>
|
||||
<td><strong>{{ c.name_family }}</strong></td>
|
||||
<td class="toCurrency has-text-grey">{{ c.net_debt }}</td>
|
||||
<td class="toCurrency has-text-success">{{ c.t_sarresid }}</td>
|
||||
<td class="toCurrency has-text-danger has-text-weight-bold">{{ c.diff }}</td>
|
||||
<td class="has-text-centered">
|
||||
<a href="{% url 'admin_panel:customer' c.id %}" target="_blank" class="button is-small is-light is-danger is-outlined rounded">ثبت چک/پیگیری</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if group1_count > 20 %}
|
||||
<p class="is-size-7 has-text-grey mt-2 has-text-centered">
|
||||
* جهت جلوگیری از کندی سیستم، صرفاً ۲۰ مشتری با <strong>بیشترین بدهی رو هوا</strong> نمایش داده شدهاند.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="notification is-success is-light has-text-centered py-4">
|
||||
🎉 عالی! هیچ مشتری بدهکاری فاقد ثبت پوشش سند (کسری چک) در سیستم وجود ندارد.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 🟡 گروه دوم: چکهای اضافی و خطای اپراتور -->
|
||||
<div class="column is-12">
|
||||
<div class="box p-4 shadow-card border-left-warning">
|
||||
<div class="level is-mobile is-flex-wrap-wrap mb-4 pb-2" style="border-bottom: 1px solid #fff3c4;">
|
||||
<div class="level-left mb-2-mobile">
|
||||
<p class="title is-6 is-size-7-mobile mb-0 has-text-warning-dark">
|
||||
<span class="icon is-small ml-1"><i class="fas fa-search-dollar"></i></span>
|
||||
گروه ۲: خطای ثبتی اپراتورها (چک پاسنشده اضافی در سیستم)
|
||||
</p>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<span class="tag is-warning is-light ml-2">جمع خطای مالی: <span class="toCurrency mr-1">{{ group2_total }}</span> تومان</span>
|
||||
<span class="tag is-warning">تعداد: {{ group2_count }} فقره</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="is-size-7 has-text-grey-dark mb-3">
|
||||
💡 <b>راهنما:</b> این افراد چک خود را نقدی یا کارتبهکارت تسویه کردهاند، اما اپراتور فراموش کرده وضعیت چک را از "پرداختنشده" به "پاسشده" تغییر دهد.
|
||||
</p>
|
||||
|
||||
{% if group2_sample %}
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable is-striped is-bordered is-size-7">
|
||||
<thead>
|
||||
<tr class="has-background-warning-light">
|
||||
<th class="has-text-centered">ردیف</th>
|
||||
<th>نام مشتری</th>
|
||||
<th>تراز واقعی مشتری</th>
|
||||
<th>چکهای ثبتشده (غلط)</th>
|
||||
<th class="has-text-danger-dark">مبلغ چک اضافه در سیستم</th>
|
||||
<th class="has-text-centered">عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in group2_sample %}
|
||||
<tr>
|
||||
<td class="has-text-centered">{{ forloop.counter }}</td>
|
||||
<td><strong>{{ c.name_family }}</strong></td>
|
||||
<td class="toCurrency has-text-info">{{ c.net_debt }}</td>
|
||||
<td class="toCurrency has-text-grey">{{ c.t_sarresid }}</td>
|
||||
<td class="toCurrency has-text-danger has-text-weight-bold">{{ c.fake_amount }}</td>
|
||||
<td class="has-text-centered">
|
||||
<a href="{% url 'admin_panel:customer' c.id %}" target="_blank" class="button is-small is-light is-warning is-outlined rounded">اصلاح چکها</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if group2_count > 20 %}
|
||||
<p class="is-size-7 has-text-grey mt-2 has-text-centered">
|
||||
* جهت جلوگیری از کندی سیستم، صرفاً ۲۰ مشتری با <strong>بیشترین میزان خطای ثبتی</strong> نمایش داده شدهاند.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="notification is-success is-light has-text-centered py-4">
|
||||
🎉 عالی! هیچ چک اضافی یا پاسنشدهی اشتباهی در سیستم یافت نشد.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 🟢 گروه سوم: پیشدریافتها -->
|
||||
<div class="column is-12">
|
||||
<div class="box p-4 shadow-card border-left-success">
|
||||
<div class="level is-mobile is-flex-wrap-wrap mb-4 pb-2" style="border-bottom: 1px solid #d4edda;">
|
||||
<div class="level-left mb-2-mobile">
|
||||
<p class="title is-6 is-size-7-mobile mb-0 has-text-success-dark">
|
||||
<span class="icon is-small ml-1"><i class="fas fa-hand-holding-usd"></i></span>
|
||||
گروه ۳: پیشدریافتها و بستانکاریهای خالص (بدون چک)
|
||||
</p>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<span class="tag is-success is-light ml-2">مجموع تعهدات: <span class="toCurrency mr-1" dir="ltr">{{ group3_total }}</span> تومان</span>
|
||||
<span class="tag is-success">تعداد: {{ group3_count }} نفر</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if group3_sample %}
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable is-striped is-bordered is-size-7">
|
||||
<thead>
|
||||
<tr class="has-background-success-light">
|
||||
<th class="has-text-centered">ردیف</th>
|
||||
<th>نام مشتری</th>
|
||||
<th>شماره تماس</th>
|
||||
<th class="has-text-success-dark">مبلغ بستانکاری (تراز منفی)</th>
|
||||
<th class="has-text-centered">وضعیت چک</th>
|
||||
<th class="has-text-centered">عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in group3_sample %}
|
||||
<tr>
|
||||
<td class="has-text-centered">{{ forloop.counter }}</td>
|
||||
<td><strong>{{ c.name_family }}</strong></td>
|
||||
<td><span dir="ltr">{{ c.phone1 }}</span></td>
|
||||
<td class="toCurrency has-text-success-dark has-text-weight-bold" dir="ltr">{{ c.net_debt }}</td>
|
||||
<td class="has-text-centered has-text-grey">ندارد</td>
|
||||
<td class="has-text-centered">
|
||||
<a href="{% url 'admin_panel:customer' c.id %}" target="_blank" class="button is-small is-light is-success is-outlined rounded">بررسی پرونده</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if group3_count > 20 %}
|
||||
<p class="is-size-7 has-text-grey mt-2 has-text-centered">
|
||||
* جهت جلوگیری از کندی سیستم، صرفاً ۲۰ مشتری بستانکار نمایش داده شدهاند.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="notification is-light has-text-centered py-4">
|
||||
هیچ پیشدریافت یا بستانکاری خالصی در سیستم یافت نشد.
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 🟣 گروه چهارم: رکوردهای نامتعارف -->
|
||||
{% if group4_count > 0 %}
|
||||
<div class="column is-12">
|
||||
<div class="box p-4 shadow-card" style="border-right: 4px solid #b86bff !important;">
|
||||
<div class="level is-mobile is-flex-wrap-wrap mb-4 pb-2" style="border-bottom: 1px solid #f3e8ff;">
|
||||
<div class="level-left mb-2-mobile">
|
||||
<p class="title is-6 is-size-7-mobile mb-0" style="color: #6b21a8;">
|
||||
<span class="icon is-small ml-1"><i class="fas fa-bug"></i></span>
|
||||
گروه ۴: رکوردهای نامتعارف (بستانکارانی که چک پرداختنشده دارند!)
|
||||
</p>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<span class="tag is-light ml-2" style="background-color: #f3e8ff; color: #6b21a8;">مجموع اختلاف: <span class="toCurrency mr-1" dir="ltr">{{ group4_total }}</span></span>
|
||||
<span class="tag" style="background-color: #b86bff; color: white;">تعداد: {{ group4_count }} مورد</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="is-size-7 has-text-grey-dark mb-3">
|
||||
💡 <b>توجه:</b> این مشتریان در دفتر کل طلبکارند، اما اپراتور چک پرداختنشدهای هم از آنها نگه داشته است! (نیاز به بررسی حسابداری دارد).
|
||||
</p>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable is-striped is-bordered is-size-7">
|
||||
<thead>
|
||||
<tr style="background-color: #faf5ff;">
|
||||
<th class="has-text-centered">ردیف</th>
|
||||
<th>نام مشتری</th>
|
||||
<th>تراز واقعی (بستانکار)</th>
|
||||
<th>چک ثبتشده</th>
|
||||
<th class="has-text-centered">عملیات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in group4_sample %}
|
||||
<tr>
|
||||
<td class="has-text-centered">{{ forloop.counter }}</td>
|
||||
<td><strong>{{ c.name_family }}</strong></td>
|
||||
<td class="toCurrency has-text-success-dark" dir="ltr">{{ c.net_debt }}</td>
|
||||
<td class="toCurrency has-text-danger">{{ c.t_sarresid }}</td>
|
||||
<td class="has-text-centered">
|
||||
<a href="{% url 'admin_panel:customer' c.id %}" target="_blank" class="button is-small is-light is-outlined rounded" style="border-color: #b86bff; color: #b86bff;">بررسی فنی</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -161,6 +432,7 @@
|
||||
.shadow-card { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04); border: 1px solid #f0f0f0; border-radius: 8px; }
|
||||
.border-left-danger { border-right: 4px solid #f14668 !important; }
|
||||
.border-left-success { border-right: 4px solid #48c78e !important; }
|
||||
.border-left-info { border-right: 4px solid #3e8ed0 !important; }
|
||||
.border-left-warning { border-right: 4px solid #ffdd57 !important; }
|
||||
.bg-light-filter { background-color: #fafafa; border-radius: 6px; }
|
||||
.h-100 { height: 100%; }
|
||||
@@ -178,22 +450,16 @@
|
||||
|
||||
/* مدیا کوئری اختصاصی برای موبایل و تبلتهای کوچک */
|
||||
@media screen and (max-width: 1023px) {
|
||||
/* در موبایل خط عمودی به خط افقی بالا تغییر میکند */
|
||||
.responsive-border-between {
|
||||
border-right: none;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 1.5rem !important;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* کاهش ارتفاع نمودارها در موبایل برای جلوگیری از اسکرول طولانی */
|
||||
.chart-wrapper-tall { height: 260px; }
|
||||
.chart-wrapper-medium { height: 240px; }
|
||||
.chart-wrapper-short { height: 200px; }
|
||||
|
||||
.mb-2-mobile { margin-bottom: 0.5rem !important; }
|
||||
|
||||
/* برای بهتر دیده شدن تگها در صفحه کوچک */
|
||||
.is-flex-grow-1-mobile { flex-grow: 1; text-align: right; }
|
||||
}
|
||||
</style>
|
||||
@@ -211,7 +477,7 @@
|
||||
autoClose: true,
|
||||
format: 'YYYY/MM/DD',
|
||||
calendar: { persian: { locale: 'fa' } },
|
||||
responsive: true // قابلیت تقویم برای موبایل
|
||||
responsive: true
|
||||
});
|
||||
|
||||
var pickerTa = $(".date-picker-ta").pDatepicker({
|
||||
@@ -226,9 +492,8 @@
|
||||
{% if filters.azTarikh %} pickerAz.setDate({{ filters.azTarikh }}); {% endif %}
|
||||
{% if filters.taTarikh %} pickerTa.setDate({{ filters.taTarikh }}); {% endif %}
|
||||
|
||||
// 🌟 نمودار روند خطی مانده حساب کل مشتریان در ۱۲ ماه
|
||||
// نمودار روند خطی
|
||||
var ctxTrend = document.getElementById('debtTrendChart').getContext('2d');
|
||||
|
||||
var gradientTrend = ctxTrend.createLinearGradient(0, 0, 0, 300);
|
||||
gradientTrend.addColorStop(0, 'rgba(62, 142, 208, 0.45)');
|
||||
gradientTrend.addColorStop(1, 'rgba(62, 142, 208, 0.0)');
|
||||
@@ -273,27 +538,14 @@
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { font: { family: 'Tahoma, Vazir', size: 10 } }
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
font: { family: 'Tahoma, Vazir', size: 10 },
|
||||
maxRotation: 45, // چرخش لیبلها برای جا شدن در موبایل
|
||||
minRotation: 0
|
||||
},
|
||||
grid: { display: false }
|
||||
}
|
||||
},
|
||||
interaction: {
|
||||
intersect: false,
|
||||
mode: 'index',
|
||||
y: { beginAtZero: true, ticks: { font: { family: 'Tahoma, Vazir', size: 10 } } },
|
||||
x: { ticks: { font: { family: 'Tahoma, Vazir', size: 10 }, maxRotation: 45, minRotation: 0 }, grid: { display: false } }
|
||||
},
|
||||
interaction: { intersect: false, mode: 'index' },
|
||||
}
|
||||
});
|
||||
|
||||
// ۲. نمودار سنی مطالبات
|
||||
// نمودار سنی مطالبات
|
||||
var ctxAging = document.getElementById('agingChart').getContext('2d');
|
||||
new Chart(ctxAging, {
|
||||
type: 'bar',
|
||||
@@ -304,7 +556,7 @@
|
||||
backgroundColor: ['rgba(72, 199, 142, 0.85)', 'rgba(255, 221, 87, 0.85)', 'rgba(241, 70, 104, 0.5)', 'rgba(241, 70, 104, 0.95)'],
|
||||
borderColor: ['#48c78e', '#ffdd57', '#f14668', '#f14668'],
|
||||
borderWidth: 1.5,
|
||||
barThickness: 30 // ضخامت میلهها برای موبایل بهتر است
|
||||
barThickness: 30
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
@@ -318,7 +570,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ۳. نمودار دایرهای تسویه مبالغ
|
||||
// نمودار دایرهای تسویه مبالغ
|
||||
var ctxPay = document.getElementById('paymentMethodsChart').getContext('2d');
|
||||
new Chart(ctxPay, {
|
||||
type: 'doughnut',
|
||||
@@ -335,14 +587,14 @@
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: window.innerWidth < 768 ? 'bottom' : 'right', // در موبایل لجند پایین میرود
|
||||
position: window.innerWidth < 768 ? 'bottom' : 'right',
|
||||
labels: { font: { family: 'Tahoma, Vazir', size: 11 }, boxWidth: 12 }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ۴. نمودار افقی انبار
|
||||
// نمودار افقی انبار
|
||||
var ctxLeak = document.getElementById('inventoryLeakChart').getContext('2d');
|
||||
new Chart(ctxLeak, {
|
||||
type: 'bar',
|
||||
@@ -367,6 +619,17 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// اسکرول نرم برای لینکهای درون صفحه
|
||||
$('a[href^="#"]').on('click', function(event) {
|
||||
var target = $(this.getAttribute('href'));
|
||||
if( target.length ) {
|
||||
event.preventDefault();
|
||||
$('html, body').stop().animate({
|
||||
scrollTop: target.offset().top - 20
|
||||
}, 800);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
+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