adding cashflow

This commit is contained in:
Flatlogic Bot 2026-02-03 03:12:53 +00:00
parent 7a5e1a7044
commit 0a02320029
6 changed files with 285 additions and 3 deletions

View File

@ -181,11 +181,11 @@
<!-- Reports Group -->
<li class="sidebar-group-header mt-2">
<a href="#reportsSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'reports' or url_name == 'customer_statement' or url_name == 'supplier_statement' %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
<a href="#reportsSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'reports' or url_name == 'customer_statement' or url_name == 'supplier_statement' or url_name == 'cashflow_report' %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
<span>{% trans "Reports" %}</span>
<i class="bi bi-chevron-down chevron"></i>
</a>
<ul class="collapse list-unstyled sub-menu {% if url_name == 'reports' or url_name == 'customer_statement' or url_name == 'supplier_statement' %}show{% endif %}" id="reportsSubmenu">
<ul class="collapse list-unstyled sub-menu {% if url_name == 'reports' or url_name == 'customer_statement' or url_name == 'supplier_statement' or url_name == 'cashflow_report' %}show{% endif %}" id="reportsSubmenu">
<li>
<a href="{% url 'reports' %}" class="{% if url_name == 'reports' %}active{% endif %}">
<i class="bi bi-graph-up-arrow"></i> {% trans "Overview Reports" %}
@ -201,6 +201,11 @@
<i class="bi bi-truck-flatbed"></i> {% trans "Supplier Statement" %}
</a>
</li>
<li>
<a href="{% url 'cashflow_report' %}" class="{% if url_name == 'cashflow_report' %}active{% endif %}">
<i class="bi bi-cash-coin"></i> {% trans "Cashflow Report" %}
</a>
</li>
</ul>
</li>

View File

@ -0,0 +1,186 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Cashflow Report" %} | {{ settings.business_name }}{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<!-- Print Header (Visible only when printing) -->
<div class="d-none d-print-block mb-4">
<div class="row align-items-center">
<div class="col-6">
{% if settings.logo %}
<img src="{{ settings.logo.url }}" alt="{{ settings.business_name }}" style="max-height: 80px;">
{% else %}
<h2 class="fw-bold mb-0">{{ settings.business_name }}</h2>
{% endif %}
</div>
<div class="col-6 text-end">
<h1 class="h3 fw-bold mb-0">{% trans "Cashflow Report" %}</h1>
<p class="text-muted mb-0">{{ start_date }} - {{ end_date }}</p>
</div>
</div>
<hr>
</div>
<div class="row mb-4 align-items-center d-print-none">
<div class="col-md-6">
<h1 class="h3 fw-bold mb-0">{% trans "Cashflow Report" %}</h1>
<p class="text-muted">{% trans "Detailed summary of all cash inflows and outflows." %}</p>
</div>
<div class="col-md-6 text-md-end">
<button onclick="window.print()" class="btn btn-outline-primary">
<i class="bi bi-printer"></i> {% trans "Print Report" %}
</button>
</div>
</div>
<!-- Filters -->
<div class="card border-0 shadow-sm mb-4 d-print-none">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "Start Date" %}</label>
<input type="date" name="start_date" class="form-control" value="{{ start_date }}">
</div>
<div class="col-md-4">
<label class="form-label small fw-bold">{% trans "End Date" %}</label>
<input type="date" name="end_date" class="form-control" value="{{ end_date }}">
</div>
<div class="col-md-4 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-filter"></i> {% trans "Filter" %}
</button>
</div>
</form>
</div>
</div>
<!-- Compact Summary for Printing -->
<div class="d-none d-print-block mb-4">
<div class="row text-center border py-3 rounded bg-light">
<div class="col-4 border-end">
<small class="text-uppercase text-muted d-block">{% trans "Total Inflow" %}</small>
<h4 class="fw-bold mb-0 text-success">{{ settings.currency_symbol }}{{ total_inflow|floatformat:settings.decimal_places }}</h4>
</div>
<div class="col-4 border-end">
<small class="text-uppercase text-muted d-block">{% trans "Total Outflow" %}</small>
<h4 class="fw-bold mb-0 text-danger">{{ settings.currency_symbol }}{{ total_outflow|floatformat:settings.decimal_places }}</h4>
</div>
<div class="col-4">
<small class="text-uppercase text-muted d-block">{% trans "Net Cashflow" %}</small>
<h4 class="fw-bold mb-0 {% if net_cashflow >= 0 %}text-primary{% else %}text-warning{% endif %}">
{{ settings.currency_symbol }}{{ net_cashflow|floatformat:settings.decimal_places }}
</h4>
</div>
</div>
</div>
<!-- Summary Cards (Screen Only) -->
<div class="row g-4 mb-4 d-print-none">
<div class="col-md-4">
<div class="card border-0 shadow-sm bg-success text-white">
<div class="card-body p-4">
<h6 class="text-uppercase small fw-bold opacity-75">{% trans "Total Inflow" %}</h6>
<h2 class="fw-bold mb-0">{{ settings.currency_symbol }}{{ total_inflow|floatformat:settings.decimal_places }}</h2>
<i class="bi bi-arrow-up-right position-absolute top-0 end-0 p-3 opacity-25" style="font-size: 2rem;"></i>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm bg-danger text-white">
<div class="card-body p-4">
<h6 class="text-uppercase small fw-bold opacity-75">{% trans "Total Outflow" %}</h6>
<h2 class="fw-bold mb-0">{{ settings.currency_symbol }}{{ total_outflow|floatformat:settings.decimal_places }}</h2>
<i class="bi bi-arrow-down-left position-absolute top-0 end-0 p-3 opacity-25" style="font-size: 2rem;"></i>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm {% if net_cashflow >= 0 %}bg-primary{% else %}bg-warning{% endif %} text-white">
<div class="card-body p-4">
<h6 class="text-uppercase small fw-bold opacity-75">{% trans "Net Cashflow" %}</h6>
<h2 class="fw-bold mb-0">{{ settings.currency_symbol }}{{ net_cashflow|floatformat:settings.decimal_places }}</h2>
<i class="bi bi-cash-stack position-absolute top-0 end-0 p-3 opacity-25" style="font-size: 2rem;"></i>
</div>
</div>
</div>
</div>
<!-- Detailed Transactions -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3 d-print-none">
<h5 class="card-title mb-0 fw-bold">{% trans "Transaction History" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4">{% trans "Date" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Reference" %}</th>
<th>{% trans "Contact" %}</th>
<th>{% trans "Method" %}</th>
<th class="text-end">{% trans "Inflow" %}</th>
<th class="text-end pe-4">{% trans "Outflow" %}</th>
</tr>
</thead>
<tbody>
{% for item in transactions %}
<tr>
<td class="ps-4">{{ item.date|date:"d M Y" }}</td>
<td>
<span class="badge {% if item.inflow > 0 %}bg-success-soft text-success{% else %}bg-danger-soft text-danger{% endif %} rounded-pill">
{{ item.type }}
</span>
</td>
<td class="fw-bold text-dark">{{ item.reference }}</td>
<td>{{ item.contact }}</td>
<td><small class="text-muted">{{ item.method }}</small></td>
<td class="text-end text-success fw-bold">
{% if item.inflow > 0 %}+{{ item.inflow|floatformat:settings.decimal_places }}{% else %}-{% endif %}
</td>
<td class="text-end text-danger fw-bold pe-4">
{% if item.outflow > 0 %}-{{ item.outflow|floatformat:settings.decimal_places }}{% else %}-{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center py-5 text-muted">
<i class="bi bi-inbox fs-1 d-block mb-3"></i>
{% trans "No transactions found for this period." %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<style>
.bg-success-soft { background-color: rgba(25, 135, 84, 0.1); }
.bg-danger-soft { background-color: rgba(220, 53, 69, 0.1); }
@media print {
.d-print-none { display: none !important; }
.d-print-block { display: block !important; }
.card { border: none !important; box-shadow: none !important; }
body { background: white !important; font-size: 12px; }
.container-fluid { padding: 0 !important; }
.table { border: 1px solid #dee2e6 !important; }
.table thead th { background-color: #f8f9fa !important; border-bottom: 2px solid #dee2e6 !important; -webkit-print-color-adjust: exact; }
.badge { border: 1px solid #ccc !important; color: black !important; background: none !important; }
.text-success { color: #198754 !important; -webkit-print-color-adjust: exact; }
.text-danger { color: #dc3545 !important; -webkit-print-color-adjust: exact; }
hr { border-top: 1px solid #000 !important; opacity: 1 !important; }
.bg-light { background-color: #f8f9fa !important; -webkit-print-color-adjust: exact; }
/* Ensure summary doesn't take too much vertical space */
.h4 { font-size: 1.25rem !important; }
.row.text-center.border { border-width: 2px !important; }
}
</style>
{% endblock %}

View File

@ -11,6 +11,7 @@ urlpatterns = [
path('reports/', views.reports, name='reports'),
path('reports/customer-statement/', views.customer_statement, name='customer_statement'),
path('reports/supplier-statement/', views.supplier_statement, name='supplier_statement'),
path('reports/cashflow/', views.cashflow_report, name='cashflow_report'),
path('settings/', views.settings_view, name='settings'),
path('profile/', views.profile_view, name='profile'),
path('users/', views.user_management, name='user_management'),

View File

@ -2119,3 +2119,93 @@ def supplier_statement(request):
'settings': settings
}
return render(request, 'core/supplier_statement.html', context)
@login_required
def cashflow_report(request):
"""
Generate a Cashflow report summarizing income and expenses.
"""
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
# Defaults to current month if no dates provided
if not start_date:
start_date = timezone.now().date().replace(day=1).strftime('%Y-%m-%d')
if not end_date:
end_date = timezone.now().date().strftime('%Y-%m-%d')
# Fetching Inflows (Sale Payments)
sale_payments = SalePayment.objects.all().select_related('sale', 'sale__customer')
if start_date:
sale_payments = sale_payments.filter(payment_date__gte=start_date)
if end_date:
sale_payments = sale_payments.filter(payment_date__lte=end_date)
# Fetching Outflows (Purchase Payments)
purchase_payments = PurchasePayment.objects.all().select_related('purchase', 'purchase__supplier')
if start_date:
purchase_payments = purchase_payments.filter(payment_date__gte=start_date)
if end_date:
purchase_payments = purchase_payments.filter(payment_date__lte=end_date)
# Fetching Outflows (Expenses)
expenses = Expense.objects.all().select_related('category', 'payment_method')
if start_date:
expenses = expenses.filter(date__gte=start_date)
if end_date:
expenses = expenses.filter(date__lte=end_date)
# Prepare detailed transactions list
transactions = []
for pay in sale_payments:
transactions.append({
'date': pay.payment_date,
'type': _('Sale Payment'),
'reference': pay.sale.invoice_number or f"Sale #{pay.sale.id}",
'contact': pay.sale.customer.name if pay.sale.customer else _('Guest'),
'inflow': float(pay.amount),
'outflow': 0,
'method': pay.payment_method_name
})
for pay in purchase_payments:
transactions.append({
'date': pay.payment_date,
'type': _('Purchase Payment'),
'reference': pay.purchase.invoice_number or f"Purchase #{pay.purchase.id}",
'contact': pay.purchase.supplier.name if pay.purchase.supplier else 'N/A',
'inflow': 0,
'outflow': float(pay.amount),
'method': pay.payment_method_name
})
for exp in expenses:
transactions.append({
'date': exp.date,
'type': _('Expense'),
'reference': exp.category.name_en,
'contact': _('Various'),
'inflow': 0,
'outflow': float(exp.amount),
'method': exp.payment_method.name_en if exp.payment_method else _('N/A')
})
transactions.sort(key=lambda x: x['date'], reverse=True)
total_inflow = sum(item['inflow'] for item in transactions)
total_outflow = sum(item['outflow'] for item in transactions)
net_cashflow = total_inflow - total_outflow
settings = SystemSetting.objects.first()
context = {
'transactions': transactions,
'total_inflow': total_inflow,
'total_outflow': total_outflow,
'net_cashflow': net_cashflow,
'start_date': start_date,
'end_date': end_date,
'settings': settings
}
return render(request, 'core/cashflow_report.html', context)