hesabresi and desc transfer

This commit is contained in:
javad committed 2026-08-16 15:42:48 +03:30
1 parent e353ed6fc2
commit 16eb747b5c
7 files changed
+678 -78

No files matched your search

+2 -2
View File
@@ -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
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
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):
+72
View File
@@ -0,0 +1,72 @@
import os
import django
from django.db.models import Sum
# تنظیمات محیطی جنگو برای اجرای اسکریپت خارج از وب‌سرور
# ⚠️ مهم: کلمه 'myproject' را با نام پوشه اصلی پروژه خود (که فایل settings.py در آن است) جایگزین کنید.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'khatibi.settings')
django.setup()
# ایمپورت مدل‌ها بعد از راه‌اندازی محیط جنگو
from admin_panel.models import Article, Sarresid, Customer
def check_financial_discrepancy():
print("\n" + "="*50)
print(" 🔍 در حال محاسبه و مقایسه اطلاعات مالی سیستم...")
print("="*50 + "\n")
# ۱. محاسبه تراز کل دفتر (mandehesab) بر اساس ستون‌های debit و credit[cite: 3]
articles_agg = Article.objects.aggregate(total_debit=Sum('debit'), total_credit=Sum('credit'))
total_debit = articles_agg['total_debit'] or 0
total_credit = articles_agg['total_credit'] or 0
total_mandehesab = total_debit - total_credit
# ۲. محاسبه مجموع اسناد پرداخت‌نشده (چک‌ها و نسیه‌ها)[cite: 3]
sarresid_agg = Sarresid.objects.filter(payStatus="false").aggregate(total_price=Sum('price'))
total_sarresid = sarresid_agg['total_price'] or 0
# ۳. محاسبه اختلاف بین دفتر کل و اسناد در دست
discrepancy = total_mandehesab - total_sarresid
print("📊 [ خلاصه وضعیت کل سیستم ]")
print("-" * 40)
print(f"💰 تراز کل دفتر کل (mandehesab): {total_mandehesab:,} ریال")
print(f"📑 مجموع اسناد و چک‌های در دست: {total_sarresid:,} ریال")
print(f"⚠️ کل اختلاف (بدهی‌های بدون سند): {discrepancy:,} ریال")
print("-" * 40 + "\n")
# ۴. تحلیل به تفکیک مشتریان برای پیدا کردن منشا اختلاف
print("🔎 [ لیست مشتریان دارای بدهی باز (منشأ اختلاف) ]")
print("-" * 40)
customers = Customer.objects.filter(is_active=True)
count = 0
for customer in customers:
# الف: محاسبه مانده حساب این مشتری
c_agg = Article.objects.filter(customer=customer).aggregate(t_d=Sum('debit'), t_c=Sum('credit'))
c_debit = c_agg['t_d'] or 0
c_credit = c_agg['t_c'] or 0
c_mandehesab = c_debit - c_credit
# ب: محاسبه سررسیدهای پرداخت نشده این مشتری
s_agg = Sarresid.objects.filter(customer=customer, payStatus="false").aggregate(t_p=Sum('price'))
c_sarresid = s_agg['t_p'] or 0
# ج: بررسی اختلاف
c_diff = c_mandehesab - c_sarresid
# اگر اختلاف بیشتر از صفر بود، یعنی مشتری بدهی رو هوا (بدون سند) دارد
if c_diff > 0:
count += 1
print(f"👤 {customer.name_family} (کد: {customer.id})")
print(f" ↳ تراز کل: {c_mandehesab:,} | اسناد: {c_sarresid:,} | مبلغ اختلاف: {c_diff:,}")
print("-" * 20)
if count == 0:
print("✅ هیچ مشتری با بدهی دفتری (بدون سند) یافت نشد. حساب‌ها تراز است.")
else:
print(f"\nتعداد کل مشتریان دارای اختلاف حساب: {count} نفر")
if __name__ == '__main__':
check_financial_discrepancy()
+18
View File
@@ -0,0 +1,18 @@
شناسه مشتری,نام و نام خانوادگی,شماره تماس,گروه خطا,شرح خطا,تراز واقعی (Article),جمع اسناد در دست (Sarresid),مبلغ مغایرت
5337,لوله گستر(ستون شنی),57999,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,700000,0,700000
4223,توکلی,9124967375,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,560000,0,560000
1819,محسن رهبر,9134712017,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,390000,0,390000
2118,وفادار,9139711662,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,366000,0,366000
4065,تقی معانی,9131724408,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,295000,0,295000
2616,رئوفی,9121279418,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,220000,0,220000
2928,مشایخی,9136588376,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,140000,0,140000
4919,رضا شهاب,9197948545,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,80000,0,80000
3943,زهرا معصومی,9132710355,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,50000,0,50000
3740,نصرالله محمدیان,9133729061,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,35000,0,35000
4098,خانم نخعی,9128178421,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,20000,0,20000
1543,گلشن زندی (مادر خانم فرخی),9133710770,گروه 1 (خطرناک),بدهی دفتری باز - بدون چک یا موعد نسیه,15000,0,15000
11417,بابک صحرایی,9123009013,گروه 2 (خطای ثبتی),چک پاس نشده در سیستم بیشتر از بدهی واقعی است,1470020000,1500000000,29980000
9699,آقا صمد (شعبه آباده),9133713616,گروه 2 (خطای ثبتی),چک پاس نشده در سیستم بیشتر از بدهی واقعی است,258100000,271000000,12900000
5889,خیری,9134711283,گروه 2 (خطای ثبتی),چک پاس نشده در سیستم بیشتر از بدهی واقعی است,0,2098000,2098000
8038, رضا اشراقی ,9133712633,گروه 2 (خطای ثبتی),چک پاس نشده در سیستم بیشتر از بدهی واقعی است,4595000,4985000,390000
7327,محمد یاسینی ***,9133717483,گروه 2 (خطای ثبتی),چک پاس نشده در سیستم بیشتر از بدهی واقعی است,550000,620000,70000
1 شناسه مشتری نام و نام خانوادگی شماره تماس گروه خطا شرح خطا تراز واقعی (Article) جمع اسناد در دست (Sarresid) مبلغ مغایرت
2 5337 لوله گستر(ستون شنی) 57999 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 700000 0 700000
3 4223 توکلی 9124967375 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 560000 0 560000
4 1819 محسن رهبر 9134712017 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 390000 0 390000
5 2118 وفادار 9139711662 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 366000 0 366000
6 4065 تقی معانی 9131724408 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 295000 0 295000
7 2616 رئوفی 9121279418 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 220000 0 220000
8 2928 مشایخی 9136588376 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 140000 0 140000
9 4919 رضا شهاب 9197948545 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 80000 0 80000
10 3943 زهرا معصومی 9132710355 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 50000 0 50000
11 3740 نصرالله محمدیان 9133729061 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 35000 0 35000
12 4098 خانم نخعی 9128178421 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 20000 0 20000
13 1543 گلشن زندی (مادر خانم فرخی) 9133710770 گروه 1 (خطرناک) بدهی دفتری باز - بدون چک یا موعد نسیه 15000 0 15000
14 11417 بابک صحرایی 9123009013 گروه 2 (خطای ثبتی) چک پاس نشده در سیستم بیشتر از بدهی واقعی است 1470020000 1500000000 29980000
15 9699 آقا صمد (شعبه آباده) 9133713616 گروه 2 (خطای ثبتی) چک پاس نشده در سیستم بیشتر از بدهی واقعی است 258100000 271000000 12900000
16 5889 خیری 9134711283 گروه 2 (خطای ثبتی) چک پاس نشده در سیستم بیشتر از بدهی واقعی است 0 2098000 2098000
17 8038 رضا اشراقی 9133712633 گروه 2 (خطای ثبتی) چک پاس نشده در سیستم بیشتر از بدهی واقعی است 4595000 4985000 390000
18 7327 محمد یاسینی *** 9133717483 گروه 2 (خطای ثبتی) چک پاس نشده در سیستم بیشتر از بدهی واقعی است 550000 620000 70000
+91
View File
@@ -0,0 +1,91 @@
import os
import csv
import django
from django.db.models import Sum, Subquery, OuterRef, F, IntegerField
from django.db.models.functions import Coalesce
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'khatibi.settings') # myproject را تغییر دهید
django.setup()
from admin_panel.models import Article, Sarresid, Customer
def run_360_audit():
print("\n" + "="*70)
print(" 🔎 کالبدشکافی ۳۶۰ درجه سیستم مالی (ترازنامه دقیق)")
print("="*70 + "\n")
# ---------------------------------------------------------
# ۱. محاسبه تراز کل سیستم (پایه ریاضی)
# ---------------------------------------------------------
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
print(f"💰 تراز کل دفتر کل (mandehesab): {global_mande:15,} ریال")
print(f"📑 مجموع چک‌های دست شرکت: {g_sar:15,} ریال")
print(f"⚠️ کل اختلاف سیستم: {global_diff:15,} ریال\n")
# ---------------------------------------------------------
# ۲. اسکن پرسرعت تمام مشتریان
# ---------------------------------------------------------
sq_deb = Article.objects.filter(customer=OuterRef('pk')).values('customer').annotate(s=Sum('debit')).values('s')
sq_cre = Article.objects.filter(customer=OuterRef('pk')).values('customer').annotate(s=Sum('credit')).values('s')
sq_sar = Sarresid.objects.filter(customer=OuterRef('pk'), payStatus="false").values('customer').annotate(s=Sum('price')).values('s')
customers = Customer.objects.annotate(
net_debt=Coalesce(Subquery(sq_deb, output_field=IntegerField()), 0) - Coalesce(Subquery(sq_cre, output_field=IntegerField()), 0),
sarresid_total=Coalesce(Subquery(sq_sar, output_field=IntegerField()), 0),
).annotate(
diff=F('net_debt') - F('sarresid_total')
)
# ---------------------------------------------------------
# ۳. دسته‌بندی ریاضیاتی (Bucketing)
# ---------------------------------------------------------
cat1_risk = 0 # 🔴 گروه 1: بدهکارانی که کسر سند/چک دارند (بدهی رو هوا)
cat2_fake_check = 0 # 🟡 گروه 2: چک پاس نشده در سیستم بیشتر از بدهی است (خطای اپراتور)
cat3_advance = 0 # 🟢 گروه 3: پیش‌دریافت (مشتری بستانکار است و چکی ندارد)
cat4_advance_check = 0 # 🟣 گروه 4: عجیب‌ترین! مشتری هم بستانکار است، هم چک پاس‌نشده دارد!
print("📊 [ تفکیک دقیق اختلاف به ۴ گروه حسابداری ]")
print("-" * 70)
for c in customers:
d = c.diff
if d > 0:
cat1_risk += d
elif d < 0:
if c.net_debt >= 0:
cat2_fake_check += d
elif c.net_debt < 0 and c.sarresid_total == 0:
cat3_advance += d
elif c.net_debt < 0 and c.sarresid_total > 0:
cat4_advance_check += d
print(f"🔴 گروه 1 (کسری چک / بدهی خطرناک): {cat1_risk:+15,} ریال [باعث مثبت شدن اختلاف]")
print(f"🟡 گروه 2 (خطای ثبتی / چک اضافی): {cat2_fake_check:+15,} ریال [باعث منفی شدن اختلاف]")
print(f"🟢 گروه 3 (پیش‌دریافت / بستانکاری خالص): {cat3_advance:+15,} ریال [باعث منفی شدن اختلاف]")
print(f"🟣 گروه 4 (بستانکارانی که چک هم داده‌اند!): {cat4_advance_check:+15,} ریال [باعث منفی شدن اختلاف]")
print("-" * 70)
calculated_diff = cat1_risk + cat2_fake_check + cat3_advance + cat4_advance_check
print(f"🧮 جمع ریاضی هر 4 گروه بالا: {calculated_diff:15,} ریال")
if calculated_diff == global_diff:
print("✅ تطابق 100%: هیچ پولی گم نشده و محاسبات از نظر حسابداری کاملاً تراز است!")
else:
print(f"❌ هنوز {global_diff - calculated_diff:,} ریال اختلاف وجود دارد!")
# ---------------------------------------------------------
# ۴. استخراج اسامی عاملین اختلاف منفی ۱۱۱ میلیونی (گروه‌های ۳ و ۴)
# ---------------------------------------------------------
print("\n🔎 [ چه کسانی باعث آن 111 میلیون اختلاف منفی شده‌اند؟ (پیش‌دریافت‌ها) ]")
bostankar_qs = customers.filter(net_debt__lt=0).order_by('net_debt')[:10]
for c in bostankar_qs:
print(f" 👤 {c.name_family} | تراز: {c.net_debt:,} | چک در دست: {c.sarresid_total:,} | تاثیر در اختلاف: {c.diff:,}")
if __name__ == '__main__':
run_360_audit()
+110
View File
@@ -0,0 +1,110 @@
import os
import csv
import datetime
# ============================================================================
# ۱. تنظیمات محیطی جنگو (بسیار مهم)
# ============================================================================
# نام 'myproject' را با نام پوشه اصلی پروژه خود (که فایل settings.py در آنجاست) عوض کنید
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'khatibi.settings')
import django
django.setup()
from django.db.models import Sum, Subquery, OuterRef, F, IntegerField
from django.db.models.functions import Coalesce
from admin_panel.models import Article, Sarresid, Customer
# ============================================================================
# ۲. هسته اصلی محاسبات (الگوریتم پرسرعت با Subquery)
# ============================================================================
def run_audit():
print("\n" + "="*65)
print(" 🚀 در حال اسکن دیتابیس و اجرای عملیات مغایرت‌گیری هوشمند...")
print("="*65 + "\n")
# واکشی سریع مجموع بدهکاری مشتریان
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')
# پردازش نهایی در لایه دیتابیس (بدون نیاز به لود کردن دیتا در RAM سرور)
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') # اختلاف تراز و اسناد
)
# استخراج گروه اول: بدهکاران مطلقاً بدون چک
group1 = base_customers.filter(net_debt__gt=0, t_sarresid=0).order_by('-net_debt')
# استخراج گروه دوم: دارای خطای چک اضافی در سیستم
group2 = base_customers.filter(diff__lt=0, t_sarresid__gt=0).annotate(
fake_amount=F('t_sarresid') - F('net_debt')
).order_by('-fake_amount')
# ============================================================================
# ۳. نمایش گزارش در کنسول
# ============================================================================
print(f"🔴 گروه اول: بدهکاران فاقد سند/چک (تعداد: {group1.count()})")
print("-" * 65)
for c in group1[:10]: # نمایش ۱۰ نفر اول در کنسول
print(f"👤 {c.name_family} | موبایل: {c.phone1} | بدهی روی هوا: {c.net_debt:,} ریال")
if group1.count() > 10:
print(f" ... و {group1.count() - 10} نفر دیگر (لیست کامل در فایل خروجی)")
print("\n" + f"🟡 گروه دوم: دارای خطای ثبت چک اضافه (تعداد: {group2.count()})")
print("-" * 65)
for c in group2[:10]:
print(f"👤 {c.name_family} | تراز واقعی: {c.net_debt:,} | مبلغ چک تو سیستم: {c.t_sarresid:,} | ❌ چک اضافی: {c.fake_amount:,}")
if group2.count() > 10:
print(f" ... و {group2.count() - 10} نفر دیگر (لیست کامل در فایل خروجی)")
# ============================================================================
# ۴. تولید فایل خروجی CSV برای حسابدار
# ============================================================================
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"financial_audit_{timestamp}.csv"
with open(filename, mode='w', newline='', encoding='utf-8-sig') as file:
writer = csv.writer(file)
# هدر فایل
writer.writerow(["شناسه مشتری", "نام و نام خانوادگی", "شماره تماس", "گروه خطا", "شرح خطا", "تراز واقعی (Article)", "جمع اسناد در دست (Sarresid)", "مبلغ مغایرت"])
# نوشتن دیتای گروه اول
for c in group1:
writer.writerow([
c.id, c.name_family, c.phone1,
"گروه 1 (خطرناک)", "بدهی دفتری باز - بدون چک یا موعد نسیه",
c.net_debt, c.t_sarresid, c.diff
])
# نوشتن دیتای گروه دوم
for c in group2:
writer.writerow([
c.id, c.name_family, c.phone1,
"گروه 2 (خطای ثبتی)", "چک پاس نشده در سیستم بیشتر از بدهی واقعی است",
c.net_debt, c.t_sarresid, c.fake_amount
])
print("\n" + "="*65)
print(f"✅ عملیات با موفقیت انجام شد. فایل گزارش تولید شد: {filename}")
print("="*65 + "\n")
if __name__ == '__main__':
run_audit()