300 lines
18 KiB
HTML
300 lines
18 KiB
HTML
{% extends 'base.html' %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Sales Invoices" %} | {{ site_settings.business_name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid px-4">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h2 class="fw-bold mb-0">{% trans "Sales Invoices" %}</h2>
|
|
<p class="text-muted small mb-0">{% trans "Track and manage your customer sales" %}</p>
|
|
</div>
|
|
<a href="{% url 'invoice_create' %}" class="btn btn-primary rounded-3 px-4 shadow-sm">
|
|
<i class="bi bi-plus-circle me-2"></i>{% trans "Create Sales Invoice" %}
|
|
</a>
|
|
</div>
|
|
|
|
{% if messages %}
|
|
<div class="mb-4">
|
|
{% for message in messages %}
|
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-3 shadow-sm border-0" role="alert">
|
|
<i class="bi bi-info-circle me-2"></i>{{ message }}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Filter Bar -->
|
|
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
|
<div class="card-body p-3">
|
|
<form method="get" class="row g-3">
|
|
<div class="col-md-3">
|
|
<label class="form-label small text-muted">{% trans "Search" %}</label>
|
|
<div class="input-group">
|
|
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span>
|
|
<input type="text" name="q" class="form-control border-start-0 rounded-end-3" placeholder="{% trans 'Customer or Invoice #' %}" value="{{ query|default:'' }}">
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small text-muted">{% trans "Start Date" %}</label>
|
|
<input type="date" name="start_date" class="form-control rounded-3" value="{{ request.GET.start_date }}">
|
|
</div>
|
|
<div class="col-md-2">
|
|
<label class="form-label small text-muted">{% trans "End Date" %}</label>
|
|
<input type="date" name="end_date" class="form-control rounded-3" value="{{ request.GET.end_date }}">
|
|
</div>
|
|
<div class="col-md-3">
|
|
<label class="form-label small text-muted">{% trans "Customer" %}</label>
|
|
<select name="customer" class="form-select rounded-3">
|
|
<option value="">{% trans "All Customers" %}</option>
|
|
{% for customer in customers %}
|
|
<option value="{{ customer.id }}" {% if request.GET.customer == customer.id|stringformat:"s" %}selected{% endif %}>
|
|
{{ customer.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-md-2 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-light w-100 me-2 rounded-3 shadow-sm">{% trans "Filter" %}</button>
|
|
<a href="{% url 'invoices' %}" class="btn btn-outline-secondary w-100 rounded-3">{% trans "Reset" %}</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card border-0 shadow-sm rounded-4">
|
|
<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 "Invoice #" %}</th>
|
|
<th>{% trans "Date" %}</th>
|
|
<th>{% trans "Customer" %}</th>
|
|
<th>{% trans "Total" %}</th>
|
|
<th>{% trans "User" %}</th>
|
|
<th>{% trans "Status" %}</th>
|
|
<th class="text-end pe-4">{% trans "Actions" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for sale in sales %}
|
|
<tr>
|
|
<td class="ps-4 fw-bold">
|
|
{{ sale.invoice_number|default:sale.id }}
|
|
</td>
|
|
<td>{{ sale.created_at|date:"Y-m-d" }}</td>
|
|
<td>
|
|
<div>{{ sale.customer.name|default:_("Guest") }}</div>
|
|
<div class="small text-muted" style="font-size: 0.75rem;">{{ sale.customer.phone|default:"" }}</div>
|
|
</td>
|
|
<td class="fw-bold text-dark">{{ site_settings.currency_symbol }}{{ sale.total_amount|floatformat:3 }}</td>
|
|
<td>
|
|
<span class="text-muted small">
|
|
<i class="bi bi-person me-1"></i>{{ sale.created_by.username|default:"System" }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
{% if sale.status == 'paid' %}
|
|
<span class="badge bg-success-subtle text-success rounded-pill px-3">{% trans "Paid" %}</span>
|
|
{% elif sale.status == 'partial' %}
|
|
<span class="badge bg-warning-subtle text-warning rounded-pill px-3">{% trans "Partial" %}</span>
|
|
{% else %}
|
|
<span class="badge bg-danger-subtle text-danger rounded-pill px-3">{% trans "Unpaid" %}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="text-end pe-4">
|
|
<div class="btn-group shadow-sm rounded-3">
|
|
{% if sale.status == 'paid' %}
|
|
<a href="{% url 'sale_receipt' sale.id %}" class="btn btn-sm btn-white border text-success" title="{% trans 'Payment Receipt' %}">
|
|
<i class="bi bi-receipt"></i>
|
|
</a>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-sm btn-white border text-success"
|
|
onclick="openWhatsAppModal('{{ sale.id }}', '{{ sale.customer.phone|default:'' }}', '{{ sale.invoice_number|default:sale.id }}')"
|
|
title="{% trans 'Send via WhatsApp' %}">
|
|
<i class="bi bi-whatsapp"></i>
|
|
</button>
|
|
<a href="{% url 'invoice_detail' sale.id %}" class="btn btn-sm btn-white border" title="{% trans 'View & Print' %}">
|
|
<i class="bi bi-printer"></i>
|
|
</a>
|
|
<a href="{% url 'edit_invoice' sale.id %}" class="btn btn-sm btn-white border" title="{% trans 'Edit' %}">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
{% if sale.balance_due > 0 %}
|
|
<button type="button" class="btn btn-sm btn-white border" data-bs-toggle="modal" data-bs-target="#paymentModal{{ sale.id }}" title="{% trans 'Record Payment' %}">
|
|
<i class="bi bi-cash-stack"></i>
|
|
</button>
|
|
{% endif %}
|
|
<button type="button" class="btn btn-sm btn-white border text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ sale.id }}">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Payment Modal -->
|
|
<div class="modal fade text-start" id="paymentModal{{ sale.id }}" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0 shadow rounded-4">
|
|
<div class="modal-header border-0">
|
|
<h5 class="fw-bold">{% trans "Record Customer Payment" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form action="{% url 'add_sale_payment' sale.id %}" method="POST">
|
|
{% csrf_token %}
|
|
<div class="modal-body">
|
|
<div class="alert alert-info border-0 rounded-3 small">
|
|
{% trans "Remaining Balance" %}: <strong>{{ site_settings.currency_symbol }}{{ sale.balance_due|floatformat:3 }}</strong>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">{% trans "Amount" %}</label>
|
|
<input type="number" step="0.001" name="amount" max="{{ sale.balance_due }}" value="{{ sale.balance_due }}" class="form-control rounded-3" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">{% trans "Payment Method" %}</label>
|
|
<select name="payment_method" class="form-select rounded-3 shadow-none">
|
|
{% for method in payment_methods %}
|
|
<option value="{{ method.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ method.name_ar }}{% else %}{{ method.name_en }}{% endif %}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">{% trans "Notes" %}</label>
|
|
<textarea name="notes" class="form-control rounded-3" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0">
|
|
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
<button type="submit" class="btn btn-primary rounded-3 px-4">{% trans "Save Payment" %}</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Modal -->
|
|
<div class="modal fade text-start" id="deleteModal{{ sale.id }}" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0 shadow rounded-4">
|
|
<div class="modal-body p-4 text-center">
|
|
<div class="text-danger mb-3">
|
|
<i class="bi bi-exclamation-octagon" style="font-size: 3rem;"></i>
|
|
</div>
|
|
<h4 class="fw-bold">{% trans "Delete Sales Invoice?" %}</h4>
|
|
<p class="text-muted">{% trans "This will restore the product quantities and delete all payment history. This action cannot be undone." %}</p>
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'delete_sale' sale.id %}" class="btn btn-danger rounded-3 py-2">{% trans "Yes, Delete" %}</a>
|
|
<button type="button" class="btn btn-light rounded-3 py-2" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="7" class="text-center py-5">
|
|
<img src="https://illustrations.popsy.co/gray/success.svg" alt="Empty" style="width: 200px;" class="mb-3">
|
|
<p class="text-muted">{% trans "No sales invoices found." %}</p>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% include "core/pagination.html" with page_obj=sales %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WhatsApp Modal -->
|
|
<div class="modal fade" id="whatsappModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0 shadow rounded-4">
|
|
<div class="modal-header border-0">
|
|
<h5 class="fw-bold">{% trans "Send Invoice via WhatsApp" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<input type="hidden" id="waSaleId">
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">{% trans "Invoice #" %}</label>
|
|
<input type="text" id="waInvoiceNum" class="form-control-plaintext fw-bold" readonly>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
|
<input type="text" id="waPhone" class="form-control rounded-3" placeholder="e.g. 628123456789">
|
|
<div class="form-text">{% trans "Enter number with country code (e.g., 62...)" %}</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0">
|
|
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
<button type="button" class="btn btn-success rounded-3 px-4" onclick="sendWhatsAppFromList()">
|
|
<span id="waSpinner" class="spinner-border spinner-border-sm d-none me-2"></span>
|
|
{% trans "Send Message" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openWhatsAppModal(saleId, phone, invoiceNum) {
|
|
document.getElementById('waSaleId').value = saleId;
|
|
document.getElementById('waPhone').value = phone;
|
|
document.getElementById('waInvoiceNum').value = invoiceNum;
|
|
new bootstrap.Modal(document.getElementById('whatsappModal')).show();
|
|
}
|
|
|
|
async function sendWhatsAppFromList() {
|
|
const saleId = document.getElementById('waSaleId').value;
|
|
const phone = document.getElementById('waPhone').value;
|
|
const btn = document.querySelector('#whatsappModal .btn-success');
|
|
const spinner = document.getElementById('waSpinner');
|
|
|
|
if (!phone) {
|
|
alert("{% trans 'Please enter a phone number.' %}");
|
|
return;
|
|
}
|
|
|
|
btn.disabled = true;
|
|
spinner.classList.remove('d-none');
|
|
|
|
try {
|
|
// Call Backend API to generate and send PDF
|
|
const response = await fetch("{% url 'send_invoice_whatsapp' %}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': '{{ csrf_token }}'
|
|
},
|
|
body: JSON.stringify({
|
|
sale_id: saleId,
|
|
phone: phone
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert('{% trans "Invoice sent via WhatsApp successfully!" %}');
|
|
const waModalEl = document.getElementById('whatsappModal');
|
|
const modalInstance = bootstrap.Modal.getInstance(waModalEl);
|
|
if (modalInstance) {
|
|
modalInstance.hide();
|
|
}
|
|
} else {
|
|
alert('{% trans "WhatsApp Error:" %} ' + data.error + '\n\n{% trans "Please check your System Settings > WhatsApp Gateway configuration." %}');
|
|
}
|
|
} catch (e) {
|
|
alert("{% trans 'An error occurred while sending the invoice.' %}");
|
|
console.error(e);
|
|
} finally {
|
|
btn.disabled = false;
|
|
spinner.classList.add('d-none');
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %} |