Autosave: 20260211-044531

This commit is contained in:
Flatlogic Bot 2026-02-11 04:45:32 +00:00
parent a9b274a48f
commit 48923270af
5 changed files with 180 additions and 13 deletions

View File

@ -219,11 +219,8 @@
page-break-after: always;
box-sizing: border-box;
/* Use Flexbox for Grid Layout - More reliable for print */
display: flex !important;
flex-wrap: wrap !important;
justify-content: flex-start !important;
align-items: flex-start !important;
/* Revert to Block + Float for reliability in all print engines */
display: block !important;
}
/* Direct print container override */
@ -238,13 +235,24 @@
box-sizing: border-box;
text-align: center;
overflow: hidden;
display: flex;
/* Float strategy for grid */
float: left !important;
/* Keep flex for INTERNAL content alignment only */
display: flex !important;
flex-direction: column;
justify-content: center;
align-items: center;
/* REMOVED width: 100% to prevent full-width expansion */
page-break-inside: avoid;
float: left; /* Fallback */
page-break-after: avoid;
margin: 0 !important;
}
/* Clearfix for sheet if needed, though usually fixed height handles it */
.label-sheet::after {
content: "";
display: table;
clear: both;
}
/* --- SHEET CONFIGURATIONS (Flexbox + Explicit Dimensions) --- */

View File

@ -112,6 +112,11 @@
<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>
@ -202,5 +207,86 @@
{% 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 {
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(data.message || "{% trans 'Message sent successfully!' %}");
bootstrap.Modal.getInstance(document.getElementById('whatsappModal')).hide();
} else {
alert(data.error || "{% trans 'Failed to send message.' %}");
}
} catch (e) {
alert("{% trans 'An error occurred.' %}");
console.error(e);
} finally {
btn.disabled = false;
spinner.classList.add('d-none');
}
}
</script>
{% endblock %}

View File

@ -15,6 +15,43 @@
</a>
</div>
<!-- Filter Form -->
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body">
<form method="GET" class="row g-3">
<div class="col-md-3">
<label class="form-label small fw-bold">{% trans "Search" %}</label>
<input type="text" name="q" class="form-control rounded-3" placeholder="{% trans 'Purchase / Invoice No' %}" value="{{ search_query }}">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold">{% trans "Date From" %}</label>
<input type="date" name="start_date" class="form-control rounded-3" value="{{ start_date }}">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold">{% trans "Date To" %}</label>
<input type="date" name="end_date" class="form-control rounded-3" value="{{ end_date }}">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
<select name="supplier" class="form-select rounded-3">
<option value="">{% trans "All Suppliers" %}</option>
{% for s in suppliers %}
<option value="{{ s.id }}" {% if selected_supplier == s.id %}selected{% endif %}>{{ s.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2 d-flex align-items-end gap-2">
<button type="submit" class="btn btn-primary w-100 rounded-3" title="{% trans 'Filter' %}">
<i class="bi bi-search"></i>
</button>
<a href="{% url 'purchases' %}" class="btn btn-light w-100 rounded-3 border" title="{% trans 'Clear' %}">
<i class="bi bi-x-lg"></i>
</a>
</div>
</form>
</div>
</div>
{% if messages %}
<div class="mb-4">
{% for message in messages %}

View File

@ -733,19 +733,55 @@ def delete_sale_return(request, pk):
# --- Purchases ---
@login_required
@login_required
def purchases(request):
purchases = Purchase.objects.all().order_by('-created_at')
# Base QuerySet
purchases_qs = Purchase.objects.select_related('supplier', 'created_by').all().order_by('-created_at')
# Filtering
search_query = request.GET.get('q', '')
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
supplier_id = request.GET.get('supplier')
if search_query:
purchases_qs = purchases_qs.filter(
Q(invoice_number__icontains=search_query) |
Q(notes__icontains=search_query) |
Q(id__icontains=search_query)
)
if start_date:
purchases_qs = purchases_qs.filter(created_at__date__gte=start_date)
if end_date:
purchases_qs = purchases_qs.filter(created_at__date__lte=end_date)
if supplier_id:
purchases_qs = purchases_qs.filter(supplier_id=supplier_id)
# Pagination
paginator = Paginator(purchases_qs, 20)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
# Context Data
payment_methods = PaymentMethod.objects.filter(is_active=True)
site_settings = SystemSetting.objects.first()
if not site_settings:
site_settings = SystemSetting.objects.create()
suppliers = Supplier.objects.all().order_by('name')
return render(request, 'core/purchases.html', {
'purchases': purchases,
'purchases': page_obj,
'payment_methods': payment_methods,
'site_settings': site_settings
'site_settings': site_settings,
'suppliers': suppliers,
'search_query': search_query,
'start_date': start_date,
'end_date': end_date,
'selected_supplier': int(supplier_id) if supplier_id and supplier_id.isdigit() else None
})
@login_required