gozaresh vosol motalebat

This commit is contained in:
javad committed 2026-08-21 13:17:59 +03:30
1 parent 0d867367f9
commit 99a86c7073
4 files changed
+404 -1

No files matched your search

@@ -94,6 +94,9 @@
گزارش
</a>
<a href="{% url 'admin_panel:report_debtors_no_vosol' %}" style="padding-right: 10px;" class="navbar-item">
گزارش وصول مطالبات
</a>
<a href="{% url 'admin_panel:inventory_movements_report' %}" style="padding-right: 10px;" class="navbar-item">
گزارش جامع انبار
@@ -0,0 +1,288 @@
{% extends "./base.html" %}
{% load custom_tags %}
{% if perms.admin_panel.ViewReportMali %}
{% block bodyPage %}
<style>
/* استایل پایه کارت‌ها */
.risk-card {
transition: transform 0.2s, box-shadow 0.2s;
border-radius: 8px;
border: 1px solid #eee;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.risk-card:hover { transform: translateY(-3px); box-shadow: 0 4px 15px rgba(0,0,0,0.05); }
.border-danger { border-bottom: 4px solid #f14668 !important; }
.border-warning { border-bottom: 4px solid #ffdd57 !important; }
/* استایل جدول و باکس توضیحات */
.table-responsive-wrapper { width: 100%; overflow-x: auto; border-radius: 8px; border: 1px solid #eaeaea; }
.table th, .table td { vertical-align: middle !important; }
.desc-box {
max-width: 250px;
white-space: normal;
background: #f8fafc;
padding: 6px 8px;
border-radius: 4px;
border-right: 3px solid #3273dc;
font-size: 0.8rem;
line-height: 1.5;
margin-top: 6px;
}
/* استایل هدرهای قابل سورت */
.sortable-th { cursor: pointer; color: #363636; text-decoration: none !important; display: inline-flex; align-items: center; gap: 5px; }
.sortable-th:hover { color: #3273dc; }
.sort-icon { font-size: 0.8rem; }
.sort-icon.active { color: #3273dc; }
.sort-icon.inactive { color: #dbdbdb; }
/* ========================================= */
/* تنظیمات اختصاصی موبایل (Responsive Design) */
/* ========================================= */
@media screen and (max-width: 768px) {
.risk-card { padding: 1rem 0.5rem !important; }
.risk-card .title { font-size: 1.1rem !important; margin-bottom: 0.2rem !important; }
.risk-card .heading { font-size: 0.65rem !important; margin-bottom: 0.5rem !important; letter-spacing: 0; }
/* بهبود نمایش کنترل پنل در موبایل */
.control-panel-box { padding: 1rem !important; }
.control-panel-box .label { font-size: 0.8rem; margin-bottom: 0.3rem; }
/* کوچک کردن فونت‌های جدول برای جا شدن بهتر در موبایل */
.table th, .table td { font-size: 0.8rem; padding: 0.5rem 0.4rem !important; }
.desc-box { font-size: 0.75rem; min-width: 180px; }
.sortable-th { font-size: 0.75rem; }
/* دکمه‌های جدول در موبایل */
.action-btn span:last-child { display: none; } /* مخفی کردن متن دکمه، فقط آیکون می‌ماند */
.action-btn { padding: 0.5rem !important; }
}
</style>
<div class="container is-fluid mt-4 mb-6">
<div class="columns m-0 is-multiline">
<div class="column is-12">
<div class="mb-5">
<h2 class="title is-5 is-size-6-mobile mb-4 has-text-grey-dark">
<i class="fas fa-chart-line has-text-info"></i>
داشبورد هوشمند وصول مطالبات (رها شده)
</h2>
<!-- داشبورد آماری دقیق و تفکیک شده -->
<div class="columns is-multiline is-mobile">
<div class="column is-6-mobile is-3-desktop">
<div class="box risk-card border-danger has-text-centered p-4">
<p class="heading has-text-grey">مشتریان هرگز پیگیری نشده</p>
<p class="title is-size-4 has-text-dark">{{ stats.never_count }} <span class="is-size-6 has-text-grey">نفر</span></p>
</div>
</div>
<div class="column is-6-mobile is-3-desktop">
<div class="box risk-card border-danger has-text-centered p-4">
<p class="heading has-text-grey">مبلغ (هرگز پیگیری نشده)</p>
<p class="title is-size-4 has-text-danger-dark toCurrency">{{ stats.never_amount }}</p>
</div>
</div>
<div class="column is-6-mobile is-3-desktop">
<div class="box risk-card border-warning has-text-centered p-4">
<p class="heading has-text-grey">رها شده (بیش از {{ current_days }} روز)</p>
<p class="title is-size-4 has-text-dark">{{ stats.expired_count }} <span class="is-size-6 has-text-grey">نفر</span></p>
</div>
</div>
<div class="column is-6-mobile is-3-desktop">
<div class="box risk-card border-warning has-text-centered p-4">
<p class="heading has-text-grey">مبلغ (پیگیری رها شده)</p>
<p class="title is-size-4 has-text-danger-dark toCurrency">{{ stats.expired_amount }}</p>
</div>
</div>
</div>
</div>
<!-- کنترل پنل فیلترها و زمان (بهینه‌شده برای موبایل) -->
<div class="box mb-4 has-background-white-ter control-panel-box">
<form method="GET" action="">
<input type="hidden" name="sort" value="{{ current_sort }}">
<input type="hidden" name="dir" value="{{ current_dir }}">
<div class="columns is-multiline is-vcentered">
<div class="column is-12-mobile is-5-tablet">
<div class="field">
<label class="label">وضعیت پیگیری:</label>
<div class="control">
<div class="select is-fullwidth">
<select name="filter_type" class="is-size-7-mobile">
<option value="all" {% if current_filter == 'all' %}selected{% endif %}>همه نیازمند پیگیری</option>
<option value="never" {% if current_filter == 'never' %}selected{% endif %}>فقط هرگز پیگیری نشده‌ها</option>
<option value="expired" {% if current_filter == 'expired' %}selected{% endif %}>فقط پیگیری‌های رها شده</option>
</select>
</div>
</div>
</div>
</div>
<div class="column is-12-mobile is-4-tablet">
<div class="field">
<label class="label">بدون اقدام بیش از (روز):</label>
<div class="control">
<input type="number" name="days" class="input is-size-7-mobile" value="{{ current_days }}" min="1">
</div>
</div>
</div>
<div class="column is-12-mobile is-3-tablet">
<div class="field">
<!-- در موبایل لیبل خالی میگذاریم تا ارتفاع حفظ شود، در دسکتاپ مخفی است -->
<label class="label is-hidden-mobile">&nbsp;</label>
<div class="control">
<button type="submit" class="button is-info is-fullwidth">
<i class="fas fa-filter ml-2"></i> اعمال فیلتر
</button>
</div>
</div>
</div>
</div>
</form>
</div>
<!-- جدول داده‌ها -->
<div class="table-responsive-wrapper shadow-sm mb-4">
<table class="table m-auto has-text-centered is-bordered is-striped is-narrow is-fullwidth is-hoverable mb-0">
<thead class="has-background-light">
<tr>
<th style="width: 40px;">#</th>
<th>نام مشتری</th>
<th>شماره تماس</th>
<th>مانده کل حساب</th>
<!-- قابلیت Sort دوطرفه -->
<th>
<a class="sortable-th" href="?filter_type={{ current_filter }}&days={{ current_days }}&sort=amount&dir={% if current_sort == 'amount' and current_dir == 'desc' %}asc{% else %}desc{% endif %}">
مبلغ معوقه
<i class="fas fa-sort{% if current_sort == 'amount' %}-{{ current_dir }}{% endif %} sort-icon {% if current_sort == 'amount' %}active{% else %}inactive{% endif %}"></i>
</a>
</th>
<th>
<a class="sortable-th" href="?filter_type={{ current_filter }}&days={{ current_days }}&sort=oldest_date&dir={% if current_sort == 'oldest_date' and current_dir == 'desc' %}asc{% else %}desc{% endif %}">
تاریخ قدیمی‌ترین تاخیر
<i class="fas fa-sort{% if current_sort == 'oldest_date' %}-{{ current_dir }}{% endif %} sort-icon {% if current_sort == 'oldest_date' %}active{% else %}inactive{% endif %}"></i>
</a>
</th>
<th style="min-width: 230px;">
<a class="sortable-th" href="?filter_type={{ current_filter }}&days={{ current_days }}&sort=last_action&dir={% if current_sort == 'last_action' and current_dir == 'desc' %}asc{% else %}desc{% endif %}">
آخرین سابقه وصول
<i class="fas fa-sort{% if current_sort == 'last_action' %}-{{ current_dir }}{% endif %} sort-icon {% if current_sort == 'last_action' %}active{% else %}inactive{% endif %}"></i>
</a>
</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
{% for customer in page_obj %}
<tr>
<td>{{ forloop.counter0|add:page_obj.start_index }}</td>
<td>
<a target="_blank" href="{% url 'admin_panel:customerNew' customer.id %}" class="has-text-link has-text-weight-bold">
{{ customer.name_family }}
</a>
</td>
<td dir="ltr" class="has-text-weight-medium">
<div class="is-size-7-mobile">{{ customer.phone1|default:"-" }}</div>
{% if customer.phone2 %}<div class="is-size-7-mobile">{{ customer.phone2 }}</div>{% endif %}
</td>
<td>
<span class="toCurrency has-text-grey-dark is-size-7">{{ customer.net_debt }}</span>
</td>
<td>
<span class="toCurrency has-text-danger has-text-weight-bold is-size-6 is-size-7-mobile">
{{ customer.overdue_amount }}
</span>
<br>
<span class="tag is-light is-danger is-small mt-1">{{ customer.overdue_count }} فقره</span>
</td>
<td class="has-text-danger-dark has-text-weight-semibold is-size-7">
{{ customer.oldest_overdue_date | toJalali }}
</td>
<td style="text-align: right;">
{% if customer.last_vosol_date %}
<div class="has-text-grey-dark is-size-7 has-text-weight-semibold">
<i class="far fa-calendar-alt ml-1"></i> {{ customer.last_vosol_date | toJalali }}
</div>
<div class="desc-box has-text-grey">
{{ customer.last_vosol_desc|default:"(بدون توضیحات ثبت شده)" }}
</div>
{% else %}
<span class="tag is-danger is-light has-text-weight-bold is-size-7-mobile">هرگز پیگیری نشده</span>
{% endif %}
</td>
<td>
<!-- استفاده از کلاس action-btn برای ریسپانسیو شدن دکمه در موبایل -->
<a target="_blank" href="{% url 'admin_panel:customerAction' customer.id %}" class="button is-small is-info is-light has-text-weight-bold action-btn">
<span class="icon is-small"><i class="fas fa-phone-alt"></i></span>
<span>ثبت تماس</span>
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="has-text-grey p-5">
<i class="fas fa-check-circle has-text-success is-size-4 mb-2"></i><br>
با این تنظیمات و فیلترها، هیچ مشتری بدهکار رها شده‌ای یافت نشد.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- صفحه‌بندی -->
<div class="mt-4 is-flex is-justify-content-center">
<div class="buttons">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}&filter_type={{ current_filter }}&days={{ current_days }}&sort={{ current_sort }}&dir={{ current_dir }}" class="button is-light is-small-mobile">صفحه قبل</a>
{% endif %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&filter_type={{ current_filter }}&days={{ current_days }}&sort={{ current_sort }}&dir={{ current_dir }}" class="button is-light is-small-mobile">صفحه بعد</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block jsEnd %}
<script>
$(document).ready(function() {
// فرمت‌کننده ایمن و رفع خطای NaN
$('.toCurrency').each(function() {
var rawValue = $(this).text().trim();
// بررسی می‌کنیم که متغیر خالی، نال یا None رشته‌ای نباشد
if(rawValue && rawValue !== 'None' && rawValue !== '') {
// حذف هر کاراکتری به جز اعداد و علامت منفی
var cleanValue = rawValue.replace(/[^\d-]/g, '');
if(!isNaN(cleanValue) && cleanValue !== '') {
$(this).text(Number(cleanValue).toLocaleString('en-US'));
} else {
$(this).text('0');
}
} else {
$(this).text('0');
}
});
});
</script>
{% endblock %}
{% endif %}