improving pos
This commit is contained in:
parent
ddd4aa0397
commit
e9c5a5c213
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -22,6 +22,9 @@ def global_settings(request):
|
||||
settings = SystemSetting.objects.first()
|
||||
if not settings:
|
||||
settings = SystemSetting.objects.create()
|
||||
return {'site_settings': settings}
|
||||
return {
|
||||
'site_settings': settings,
|
||||
'decimal_places': settings.decimal_places if settings else 3
|
||||
}
|
||||
except:
|
||||
return {}
|
||||
return {'decimal_places': 3}
|
||||
18
core/migrations/0012_systemsetting_decimal_places.py
Normal file
18
core/migrations/0012_systemsetting_decimal_places.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-02 16:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0011_paymentmethod_purchasepayment_payment_method_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='systemsetting',
|
||||
name='decimal_places',
|
||||
field=models.PositiveSmallIntegerField(default=3, verbose_name='Decimal Places'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -275,9 +275,10 @@ class SystemSetting(models.Model):
|
||||
email = models.EmailField(_("Email"), blank=True)
|
||||
currency_symbol = models.CharField(_("Currency Symbol"), max_length=10, default="OMR")
|
||||
tax_rate = models.DecimalField(_("Tax Rate (%)"), max_digits=5, decimal_places=2, default=0)
|
||||
decimal_places = models.PositiveSmallIntegerField(_("Decimal Places"), default=3)
|
||||
logo = models.ImageField(_("Logo"), upload_to="business_logos/", blank=True, null=True)
|
||||
vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True)
|
||||
registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.business_name
|
||||
return self.business_name
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<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">
|
||||
<table class="table table-hover align-middle mb-0" id="customersTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Name" %}</th>
|
||||
@ -65,89 +65,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- View Customer Modal -->
|
||||
<div class="modal fade" id="viewCustomerModal{{ customer.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Customer Details" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-center mb-4">
|
||||
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<i class="bi bi-person" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-0">{{ customer.name }}</h4>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Phone" %}</small>
|
||||
<span class="fw-bold">{{ customer.phone|default:"-" }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Email" %}</small>
|
||||
<span class="fw-bold">{{ customer.email|default:"-" }}</span>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<small class="text-muted d-block">{% trans "Address" %}</small>
|
||||
<span class="fw-bold">{{ customer.address|default:"-" }}</span>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<small class="text-muted d-block">{% trans "Total Sales" %}</small>
|
||||
<span class="badge bg-primary bg-opacity-10 text-primary fs-6">{{ site_settings.currency_symbol }}{{ customer.total_sales|default:"0.000"|floatformat:3 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Customer Modal -->
|
||||
<div class="modal fade" id="editCustomerModal{{ customer.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit Customer" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'edit_customer' customer.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Full Name" %}</label>
|
||||
<input type="text" name="name" class="form-control rounded-3" value="{{ customer.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" name="phone" class="form-control rounded-3" value="{{ customer.phone }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Email Address" %}</label>
|
||||
<input type="email" name="email" class="form-control rounded-3" value="{{ customer.email }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Address" %}</label>
|
||||
<textarea name="address" class="form-control rounded-3" rows="3">{{ customer.address }}</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 "Update Customer" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-5">
|
||||
<i class="bi bi-person text-muted opacity-25" style="font-size: 3rem;"></i>
|
||||
<p class="mt-2 text-muted">{% trans "No customers found." %}</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -164,32 +81,68 @@
|
||||
<h5 class="modal-title fw-bold">{% trans "Add New Customer" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'add_customer' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Full Name" %}</label>
|
||||
<input type="text" name="name" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" name="phone" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Email Address" %}</label>
|
||||
<input type="email" name="email" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Address" %}</label>
|
||||
<textarea name="address" class="form-control rounded-3" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Full Name" %}</label>
|
||||
<input type="text" id="addCustName" class="form-control rounded-3" required>
|
||||
</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 Customer" %}</button>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" id="addCustPhone" class="form-control rounded-3">
|
||||
</div>
|
||||
</form>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Email Address" %}</label>
|
||||
<input type="email" id="addCustEmail" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Address" %}</label>
|
||||
<textarea id="addCustAddress" class="form-control rounded-3" rows="3"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-outline-primary rounded-3" id="saveAndAddAnotherCust">{% trans "Save & Add Another" %}</button>
|
||||
<button type="button" class="btn btn-primary rounded-3 px-4" id="saveCust">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
async function saveCustomer(stayOpen = false) {
|
||||
const name = document.getElementById('addCustName').value;
|
||||
const phone = document.getElementById('addCustPhone').value;
|
||||
const email = document.getElementById('addCustEmail').value;
|
||||
const address = document.getElementById('addCustAddress').value;
|
||||
|
||||
if (!name) return alert('Please fill customer name');
|
||||
|
||||
const response = await fetch('{% url "add_customer_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, phone, email, address })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
if (stayOpen) {
|
||||
document.getElementById('addCustName').value = '';
|
||||
document.getElementById('addCustPhone').value = '';
|
||||
document.getElementById('addCustEmail').value = '';
|
||||
document.getElementById('addCustAddress').value = '';
|
||||
alert('{% trans "Customer added. You can add another one." %}');
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('saveCust').onclick = () => saveCustomer(false);
|
||||
document.getElementById('saveAndAddAnotherCust').onclick = () => saveCustomer(true);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -92,7 +92,7 @@
|
||||
<!-- Product Table (Grid) -->
|
||||
<div class="card border-0 shadow-sm rounded-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0" id="productsTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Item" %}</th>
|
||||
@ -153,184 +153,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- View Product Modal -->
|
||||
<div class="modal fade" id="viewProductModal{{ product.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Item Details" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4 text-center mb-3">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" class="img-fluid rounded-4 shadow-sm" alt="">
|
||||
{% else %}
|
||||
<div class="bg-light rounded-4 d-flex align-items-center justify-content-center" style="height: 200px;">
|
||||
<i class="bi bi-box-seam text-muted opacity-25" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h3 class="fw-bold mb-1">{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}</h3>
|
||||
<p class="text-muted mb-3">{{ product.sku }} | {% if LANGUAGE_CODE == 'ar' %}{{ product.category.name_ar }}{% else %}{{ product.category.name_en }}{% endif %}</p>
|
||||
<hr>
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Sale Price" %}</small>
|
||||
<span class="h5 fw-bold text-primary">{{ site_settings.currency_symbol }}{{ product.price|floatformat:3 }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Cost Price" %}</small>
|
||||
<span class="h5 fw-bold">{{ site_settings.currency_symbol }}{{ product.cost_price|floatformat:3 }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Stock Quantity" %}</small>
|
||||
<span class="badge {% if product.stock_quantity < 5 %}bg-danger{% else %}bg-success{% endif %}">{{ product.stock_quantity }} {{ product.unit.short_name|default:"" }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "VAT" %}</small>
|
||||
<span>{{ product.vat }}%</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Supplier" %}</small>
|
||||
<span>{{ product.supplier.name|default:"-" }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Status" %}</small>
|
||||
{% if product.is_active %}<span class="text-success">{% trans "Active" %}</span>{% else %}<span class="text-danger">{% trans "Inactive" %}</span>{% endif %}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Entry Date" %}</small>
|
||||
<span>{{ product.created_at|date:"Y-m-d H:i" }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Opening Stock" %}</small>
|
||||
<span>{{ product.opening_stock }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Product Modal -->
|
||||
<div class="modal fade" id="editProductModal{{ product.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit Item" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'edit_product' product.id %}" method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<!-- Identity Section -->
|
||||
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-2">{% trans "Basic Information" %}</h6><hr class="my-2"></div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" name="name_en" class="form-control rounded-3" value="{{ product.name_en }}" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" name="name_ar" class="form-control rounded-3" value="{{ product.name_ar }}" dir="rtl" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Barcode / SKU" %}</label>
|
||||
<input type="text" name="sku" class="form-control rounded-3" value="{{ product.sku }}" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Category" %}</label>
|
||||
<select name="category" class="form-select rounded-3" required>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}" {% if product.category.id == category.id %}selected{% endif %}>
|
||||
{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Unit" %}</label>
|
||||
<select name="unit" class="form-select rounded-3">
|
||||
<option value="">{% trans "Select Unit" %}</option>
|
||||
{% for unit in units %}
|
||||
<option value="{{ unit.id }}" {% if product.unit.id == unit.id %}selected{% endif %}>
|
||||
{% if LANGUAGE_CODE == 'ar' %}{{ unit.name_ar }}{% else %}{{ unit.name_en }}{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
|
||||
<select name="supplier" class="form-select rounded-3">
|
||||
<option value="">{% trans "Select Supplier" %}</option>
|
||||
{% for supplier in suppliers %}
|
||||
<option value="{{ supplier.id }}" {% if product.supplier.id == supplier.id %}selected{% endif %}>
|
||||
{{ supplier.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Change Picture" %}</label>
|
||||
<input type="file" name="image" class="form-control rounded-3" accept="image/*">
|
||||
{% if product.image %}
|
||||
<small class="text-muted">{% trans "Current" %}: {{ product.image.name }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Pricing & Stock Section -->
|
||||
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-3">{% trans "Pricing & Stock" %}</h6><hr class="my-2"></div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Cost Price" %}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0">{{ site_settings.currency_symbol }}</span>
|
||||
<input type="number" step="0.001" name="cost_price" class="form-control rounded-3 border-start-0" value="{{ product.cost_price|floatformat:3 }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Sale Price" %}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0">{{ site_settings.currency_symbol }}</span>
|
||||
<input type="number" step="0.001" name="price" class="form-control rounded-3 border-start-0" value="{{ product.price|floatformat:3 }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "VAT (%)" %}</label>
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" name="vat" class="form-control rounded-3" value="{{ product.vat|floatformat:2 }}" required>
|
||||
<span class="input-group-text bg-light">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Opening Stock" %}</label>
|
||||
<input type="number" name="opening_stock" class="form-control rounded-3" value="{{ product.opening_stock }}" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "In Stock (Current)" %}</label>
|
||||
<input type="number" name="stock_quantity" class="form-control rounded-3" value="{{ product.stock_quantity }}" required>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-center pt-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="is_active" id="editActiveSwitch{{ product.id }}" {% if product.is_active %}checked{% endif %}>
|
||||
<label class="form-check-label fw-bold small" for="editActiveSwitch{{ product.id }}">{% trans "Active / Visible" %}</label>
|
||||
</div>
|
||||
</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="submit" class="btn btn-primary rounded-3 px-4">{% trans "Update Product" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-5">
|
||||
@ -349,7 +171,7 @@
|
||||
<div class="tab-pane fade" id="categories-list" role="tabpanel">
|
||||
<div class="card border-0 shadow-sm rounded-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0" id="categoriesTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Category Name" %}</th>
|
||||
@ -375,37 +197,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Edit Category Modal -->
|
||||
<div class="modal fade" id="editCategoryModal{{ category.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit Category" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'edit_category' category.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" name="name_en" class="form-control rounded-3" value="{{ category.name_en }}" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" name="name_ar" class="form-control rounded-3" value="{{ category.name_ar }}" dir="rtl" required>
|
||||
</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="submit" class="btn btn-primary rounded-3 px-4">{% trans "Update Category" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">{% trans "No categories found." %}</td>
|
||||
@ -421,7 +212,7 @@
|
||||
<div class="tab-pane fade" id="units-list" role="tabpanel">
|
||||
<div class="card border-0 shadow-sm rounded-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0" id="unitsTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Unit Name" %}</th>
|
||||
@ -447,40 +238,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Edit Unit Modal -->
|
||||
<div class="modal fade" id="editUnitModal{{ unit.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit Unit" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'edit_unit' unit.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" name="name_en" class="form-control rounded-3" value="{{ unit.name_en }}" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" name="name_ar" class="form-control rounded-3" value="{{ unit.name_ar }}" dir="rtl" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Short Name" %}</label>
|
||||
<input type="text" name="short_name" class="form-control rounded-3" value="{{ unit.short_name }}" required>
|
||||
</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="submit" class="btn btn-primary rounded-3 px-4">{% trans "Update Unit" %}</button>
|
||||
</div>
|
||||
</form> </div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-4 text-muted">{% trans "No units found." %}</td>
|
||||
@ -494,6 +251,70 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modals -->
|
||||
|
||||
<!-- Add Category Modal -->
|
||||
<div class="modal fade" id="addCategoryModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Add New Category" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" id="addCatNameEn" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" id="addCatNameAr" class="form-control rounded-3" dir="rtl" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-outline-primary rounded-3" id="saveAndAddAnotherCat">{% trans "Save & Add Another" %}</button>
|
||||
<button type="button" class="btn btn-primary rounded-3 px-4" id="saveCat">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Unit Modal -->
|
||||
<div class="modal fade" id="addUnitModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Add New Unit" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" id="addUnitNameEn" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" id="addUnitNameAr" class="form-control rounded-3" dir="rtl" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Short Name (e.g., kg, pcs)" %}</label>
|
||||
<input type="text" id="addUnitShortName" class="form-control rounded-3" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-outline-primary rounded-3" id="saveAndAddAnotherUnit">{% trans "Save & Add Another" %}</button>
|
||||
<button type="button" class="btn btn-primary rounded-3 px-4" id="saveUnit">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Product Modal -->
|
||||
<div class="modal fade" id="addProductModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
@ -506,7 +327,6 @@
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<!-- Identity Section -->
|
||||
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-2">{% trans "Basic Information" %}</h6><hr class="my-2"></div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
@ -519,96 +339,40 @@
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Barcode / SKU" %}</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="sku" id="skuInput" class="form-control rounded-3" placeholder="{% trans 'Auto-generated if empty' %}">
|
||||
<button class="btn btn-outline-primary" type="button" id="suggestSkuBtn" title="{% trans 'Suggest SKU' %}">
|
||||
<i class="bi bi-magic"></i>
|
||||
</button>
|
||||
<input type="text" name="sku" id="skuInput" class="form-control rounded-3">
|
||||
<button class="btn btn-outline-primary" type="button" id="suggestSkuBtn"><i class="bi bi-magic"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Category" %}</label>
|
||||
<div class="input-group">
|
||||
<select name="category" id="categorySelect" class="form-select rounded-3" required>
|
||||
<option value="">{% trans "Select Category" %}</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-outline-primary" type="button" onclick="bootstrap.Modal.getOrCreateInstance('#quickAddCategoryModal').show()">
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<select name="category" id="categorySelect" class="form-select rounded-3" required>
|
||||
<option value="">{% trans "Select Category" %}</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ category.name_ar }}{% else %}{{ category.name_en }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Unit" %}</label>
|
||||
<div class="input-group">
|
||||
<select name="unit" id="unitSelect" class="form-select rounded-3">
|
||||
<option value="">{% trans "Select Unit" %}</option>
|
||||
{% for unit in units %}
|
||||
<option value="{{ unit.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ unit.name_ar }}{% else %}{{ unit.name_en }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-outline-primary" type="button" onclick="bootstrap.Modal.getOrCreateInstance('#quickAddUnitModal').show()">
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
<select name="unit" id="unitSelect" class="form-select rounded-3">
|
||||
<option value="">{% trans "Select Unit" %}</option>
|
||||
{% for unit in units %}
|
||||
<option value="{{ unit.id }}">{% if LANGUAGE_CODE == 'ar' %}{{ unit.name_ar }}{% else %}{{ unit.name_en }}{% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Supplier" %}</label>
|
||||
<div class="input-group">
|
||||
<select name="supplier" id="supplierSelect" class="form-select rounded-3">
|
||||
<option value="">{% trans "Select Supplier" %}</option>
|
||||
{% for supplier in suppliers %}
|
||||
<option value="{{ supplier.id }}">{{ supplier.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-outline-primary" type="button" onclick="bootstrap.Modal.getOrCreateInstance('#quickAddSupplierModal').show()">
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Product Picture" %}</label>
|
||||
<input type="file" name="image" class="form-control rounded-3" accept="image/*">
|
||||
</div>
|
||||
|
||||
<!-- Pricing & Stock Section -->
|
||||
<div class="col-12"><h6 class="fw-bold text-primary mb-0 mt-3">{% trans "Pricing & Stock" %}</h6><hr class="my-2"></div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Cost Price" %}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0">{{ site_settings.currency_symbol }}</span>
|
||||
<input type="number" step="0.001" name="cost_price" class="form-control rounded-3 border-start-0" value="0.000" required>
|
||||
</div>
|
||||
<input type="number" step="0.001" name="cost_price" class="form-control rounded-3" value="0.000" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Sale Price" %}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-light border-end-0">{{ site_settings.currency_symbol }}</span>
|
||||
<input type="number" step="0.001" name="price" class="form-control rounded-3 border-start-0" value="0.000" required>
|
||||
</div>
|
||||
<input type="number" step="0.001" name="price" class="form-control rounded-3" value="0.000" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "VAT (%)" %}</label>
|
||||
<div class="input-group">
|
||||
<input type="number" step="0.01" name="vat" class="form-control rounded-3" value="0.00" required>
|
||||
<span class="input-group-text bg-light">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "Opening Stock" %}</label>
|
||||
<input type="number" name="opening_stock" class="form-control rounded-3" value="0" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold">{% trans "In Stock (Current)" %}</label>
|
||||
<label class="form-label small fw-bold">{% trans "Stock" %}</label>
|
||||
<input type="number" name="stock_quantity" class="form-control rounded-3" value="0" required>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-center pt-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="is_active" id="isActiveSwitch" checked>
|
||||
<label class="form-check-label fw-bold small" for="isActiveSwitch">{% trans "Active / Visible" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
@ -620,293 +384,117 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Add Category Modal -->
|
||||
<div class="modal fade" id="quickAddCategoryModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Quick Add Category" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" id="quickCatNameEn" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" id="quickCatNameAr" class="form-control rounded-3" dir="rtl" required>
|
||||
</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-primary rounded-3 px-4" id="saveQuickCategory">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Add Unit Modal -->
|
||||
<div class="modal fade" id="quickAddUnitModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Quick Add Unit" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" id="quickUnitNameEn" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" id="quickUnitNameAr" class="form-control rounded-3" dir="rtl" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Short Name" %}</label>
|
||||
<input type="text" id="quickUnitShortName" class="form-control rounded-3" required>
|
||||
</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-primary rounded-3 px-4" id="saveQuickUnit">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Add Supplier Modal -->
|
||||
<div class="modal fade" id="quickAddSupplierModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Quick Add Supplier" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Supplier Name" %}</label>
|
||||
<input type="text" id="quickSuppName" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Contact Person" %}</label>
|
||||
<input type="text" id="quickSuppContact" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">{% trans "Phone" %}</label>
|
||||
<input type="text" id="quickSuppPhone" class="form-control rounded-3">
|
||||
</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-primary rounded-3 px-4" id="saveQuickSupplier">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Import Modal -->
|
||||
<div class="modal fade" id="importModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Import Items from Excel" %}</h5>
|
||||
<h5 class="modal-title fw-bold">{% trans "Import Items" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'import_products' %}" method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info rounded-3 small">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
{% trans "Excel file should have these columns in order:" %}<br>
|
||||
<strong>{% trans "Name (Eng), Name (Ar), SKU, Cost Price, Sale Price" %}</strong>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Select Excel File (.xlsx)" %}</label>
|
||||
<label class="form-label small fw-bold">{% trans "Excel File (.xlsx)" %}</label>
|
||||
<input type="file" name="excel_file" class="form-control rounded-3" accept=".xlsx" required>
|
||||
</div>
|
||||
<p class="text-muted small">
|
||||
{% trans "Note: If SKU exists, the item will be updated. Otherwise, a new item will be created in the 'General' category." %}
|
||||
</p>
|
||||
</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-success rounded-3 px-4">{% trans "Start Import" %}</button>
|
||||
<button type="submit" class="btn btn-success rounded-3 px-4">{% trans "Import" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Category Modal -->
|
||||
<div class="modal fade" id="addCategoryModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Add New Category" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'add_category' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" name="name_en" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" name="name_ar" class="form-control rounded-3" dir="rtl" required>
|
||||
</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="submit" class="btn btn-primary rounded-3 px-4">{% trans "Save Category" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Unit Modal -->
|
||||
<div class="modal fade" id="addUnitModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Add New Unit" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'add_unit' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" name="name_en" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" name="name_ar" class="form-control rounded-3" dir="rtl" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold">{% trans "Short Name (e.g., kg, pcs)" %}</label>
|
||||
<input type="text" name="short_name" class="form-control rounded-3" required>
|
||||
</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="submit" class="btn btn-primary rounded-3 px-4">{% trans "Save Unit" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Persist active tab on reload var hash = window.location.hash; if (hash) { var triggerEl = document.querySelector('button[data-bs-target="' + hash + '"]'); if (triggerEl) { var tab = new bootstrap.Tab(triggerEl); tab.show(); } } var tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]'); tabEls.forEach(function(el) { el.addEventListener('shown.bs.tab', function(event) { window.location.hash = event.target.getAttribute('data-bs-target'); }); });
|
||||
// Fix for multiple modals: Ensure body class is preserved
|
||||
document.addEventListener('hidden.bs.modal', function () {
|
||||
if (document.querySelectorAll('.modal.show').length > 0) {
|
||||
document.body.classList.add('modal-open');
|
||||
// Tab persistence
|
||||
var hash = window.location.hash;
|
||||
if (hash) {
|
||||
var triggerEl = document.querySelector('button[data-bs-target="' + hash + '"]');
|
||||
if (triggerEl) {
|
||||
var tab = new bootstrap.Tab(triggerEl);
|
||||
tab.show();
|
||||
}
|
||||
}
|
||||
var tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]');
|
||||
tabEls.forEach(function(el) {
|
||||
el.addEventListener('shown.bs.tab', function(event) {
|
||||
window.location.hash = event.target.getAttribute('data-bs-target');
|
||||
});
|
||||
});
|
||||
|
||||
// Suggest SKU
|
||||
const suggestBtn = document.getElementById('suggestSkuBtn');
|
||||
if (suggestBtn) {
|
||||
suggestBtn.addEventListener('click', function() {
|
||||
fetch('{% url "suggest_sku" %}')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('skuInput').value = data.sku;
|
||||
})
|
||||
.catch(error => console.error('Error fetching SKU:', error));
|
||||
});
|
||||
}
|
||||
|
||||
// Quick Add Category
|
||||
document.getElementById('saveQuickCategory').addEventListener('click', function() {
|
||||
const nameEn = document.getElementById('quickCatNameEn').value;
|
||||
const nameAr = document.getElementById('quickCatNameAr').value;
|
||||
// AJAX Save Functions
|
||||
async function saveCategory(stayOpen = false) {
|
||||
const nameEn = document.getElementById('addCatNameEn').value;
|
||||
const nameAr = document.getElementById('addCatNameAr').value;
|
||||
if (!nameEn || !nameAr) return alert('Please fill all fields');
|
||||
|
||||
fetch('{% url "add_category_ajax" %}', {
|
||||
const response = await fetch('{% url "add_category_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name_en: nameEn, name_ar: nameAr })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const select = document.getElementById('categorySelect');
|
||||
const option = new Option(data.name_en + ' | ' + data.name_ar, data.id, true, true);
|
||||
select.add(option);
|
||||
bootstrap.Modal.getInstance(document.getElementById('quickAddCategoryModal')).hide();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
if (stayOpen) {
|
||||
document.getElementById('addCatNameEn').value = '';
|
||||
document.getElementById('addCatNameAr').value = '';
|
||||
alert('{% trans "Category added. You can add another one." %}');
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
}
|
||||
|
||||
// Quick Add Unit
|
||||
document.getElementById('saveQuickUnit').addEventListener('click', function() {
|
||||
const nameEn = document.getElementById('quickUnitNameEn').value;
|
||||
const nameAr = document.getElementById('quickUnitNameAr').value;
|
||||
const shortName = document.getElementById('quickUnitShortName').value;
|
||||
async function saveUnit(stayOpen = false) {
|
||||
const nameEn = document.getElementById('addUnitNameEn').value;
|
||||
const nameAr = document.getElementById('addUnitNameAr').value;
|
||||
const shortName = document.getElementById('addUnitShortName').value;
|
||||
if (!nameEn || !nameAr || !shortName) return alert('Please fill all fields');
|
||||
|
||||
fetch('{% url "add_unit_ajax" %}', {
|
||||
const response = await fetch('{% url "add_unit_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name_en: nameEn, name_ar: nameAr, short_name: shortName })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const select = document.getElementById('unitSelect');
|
||||
const option = new Option(data.name_en + ' | ' + data.name_ar, data.id, true, true);
|
||||
select.add(option);
|
||||
bootstrap.Modal.getInstance(document.getElementById('quickAddUnitModal')).hide();
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Quick Add Supplier
|
||||
document.getElementById('saveQuickSupplier').addEventListener('click', function() {
|
||||
const name = document.getElementById('quickSuppName').value;
|
||||
const contact = document.getElementById('quickSuppContact').value;
|
||||
const phone = document.getElementById('quickSuppPhone').value;
|
||||
if (!name) return alert('Please fill supplier name');
|
||||
|
||||
fetch('{% url "add_supplier_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name, contact_person: contact, phone: phone })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const select = document.getElementById('supplierSelect');
|
||||
const option = new Option(data.name, data.id, true, true);
|
||||
select.add(option);
|
||||
bootstrap.Modal.getInstance(document.getElementById('quickAddSupplierModal')).hide();
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
if (stayOpen) {
|
||||
document.getElementById('addUnitNameEn').value = '';
|
||||
document.getElementById('addUnitNameAr').value = '';
|
||||
document.getElementById('addUnitShortName').value = '';
|
||||
alert('{% trans "Unit added. You can add another one." %}');
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('saveCat').onclick = () => saveCategory(false);
|
||||
document.getElementById('saveAndAddAnotherCat').onclick = () => saveCategory(true);
|
||||
document.getElementById('saveUnit').onclick = () => saveUnit(false);
|
||||
document.getElementById('saveAndAddAnotherUnit').onclick = () => saveUnit(true);
|
||||
|
||||
// SKU Suggestion
|
||||
const suggestBtn = document.getElementById('suggestSkuBtn');
|
||||
if (suggestBtn) {
|
||||
suggestBtn.onclick = () => {
|
||||
fetch('{% url "suggest_sku" %}')
|
||||
.then(r => r.json())
|
||||
.then(d => document.getElementById('skuInput').value = d.sku);
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -9,11 +9,12 @@
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
border: none;
|
||||
border-radius: 15px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.1) !important;
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 15px rgba(0,0,0,0.1) !important;
|
||||
}
|
||||
.cart-container {
|
||||
position: sticky;
|
||||
@ -28,18 +29,30 @@
|
||||
}
|
||||
.category-badge {
|
||||
cursor: pointer;
|
||||
padding: 8px 15px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
margin-right: 4px;
|
||||
margin-bottom: 4px;
|
||||
display: inline-block;
|
||||
background: #f8f9fa;
|
||||
color: #6c757d;
|
||||
background: #f1f3f5;
|
||||
color: #495057;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.category-badge.active {
|
||||
background: var(--bs-primary);
|
||||
color: white;
|
||||
border-color: var(--bs-primary);
|
||||
}
|
||||
.product-name {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
height: 2.4em;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
/* Invoice Print Styles */
|
||||
@ -106,11 +119,11 @@
|
||||
<h4 class="fw-bold mb-0">{% trans "Point of Sale" %}</h4>
|
||||
<div class="input-group w-50">
|
||||
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search"></i></span>
|
||||
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="{% trans 'Search products...' %}">
|
||||
<input type="text" id="productSearch" class="form-control border-start-0 shadow-none" placeholder="{% trans 'Search products...' %}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="mb-4 overflow-auto text-nowrap pb-2">
|
||||
<div class="category-badge active" data-category="all">{% trans "All" %}</div>
|
||||
{% for category in categories %}
|
||||
<div class="category-badge" data-category="{{ category.id }}">
|
||||
@ -119,23 +132,23 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="row row-cols-1 row-cols-md-3 g-3" id="productGrid">
|
||||
<div class="row row-cols-2 row-cols-sm-3 row-cols-md-4 row-cols-lg-5 row-cols-xl-6 g-2" id="productGrid">
|
||||
{% for product in products %}
|
||||
<div class="col product-item" data-category="{{ product.category.id }}" data-name-en="{{ product.name_en|lower }}" data-name-ar="{{ product.name_ar }}">
|
||||
<div class="card h-100 shadow-sm product-card p-2" onclick="addToCart({{ product.id }}, '{{ product.name_en|escapejs }}', '{{ product.name_ar|escapejs }}', {{ product.price }})">
|
||||
<div class="card h-100 shadow-sm product-card p-1" onclick="addToCart({{ product.id }}, '{{ product.name_en|escapejs }}', '{{ product.name_ar|escapejs }}', {{ product.price }})">
|
||||
{% if product.image %}
|
||||
<img src="{{ product.image.url }}" class="card-img-top rounded-3" alt="{{ product.name_en }}" style="height: 150px; object-fit: cover;">
|
||||
<img src="{{ product.image.url }}" class="card-img-top rounded-3" alt="{{ product.name_en }}" style="height: 80px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-light rounded-3 d-flex align-items-center justify-content-center" style="height: 150px;">
|
||||
<i class="bi bi-image text-muted opacity-25" style="font-size: 3rem;"></i>
|
||||
<div class="bg-light rounded-3 d-flex align-items-center justify-content-center" style="height: 80px;">
|
||||
<i class="bi bi-image text-muted opacity-25" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body p-2 text-center">
|
||||
<h6 class="fw-bold mb-1">
|
||||
<div class="card-body p-1 text-center">
|
||||
<div class="product-name fw-bold mb-1">
|
||||
{% if LANGUAGE_CODE == 'ar' %}{{ product.name_ar }}{% else %}{{ product.name_en }}{% endif %}
|
||||
</h6>
|
||||
<p class="text-primary fw-bold mb-0">{{ site_settings.currency_symbol }}{{ product.price|floatformat:3 }}</p>
|
||||
<small class="text-muted">{% trans "Stock" %}: {{ product.stock_quantity }}</small>
|
||||
</div>
|
||||
<p class="text-primary fw-bold mb-0" style="font-size: 0.8rem;">{{ site_settings.currency_symbol }}{{ product.price|floatformat:decimal_places }}</p>
|
||||
<small class="text-muted d-block" style="font-size: 0.65rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{% trans "Stock" %}: {{ product.stock_quantity }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -146,17 +159,28 @@
|
||||
<!-- Cart Section -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-0 shadow-sm rounded-4 cart-container">
|
||||
<div class="card-header bg-white border-0 pt-4 px-4">
|
||||
<h5 class="fw-bold">{% trans "Current Order" %}</h5>
|
||||
<select id="customerSelect" class="form-select form-select-sm mt-3">
|
||||
<option value="">{% trans "Walking Customer" %}</option>
|
||||
{% for customer in customers %}
|
||||
<option value="{{ customer.id }}">{{ customer.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="card-header bg-white border-0 pt-4 px-4 d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold mb-0">{% trans "Current Order" %}</h5>
|
||||
<button class="btn btn-sm btn-link text-danger text-decoration-none p-0" onclick="clearCart()">
|
||||
<i class="bi bi-trash"></i> {% trans "Clear" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="card-body px-4 py-2 cart-items">
|
||||
<div class="px-4 mt-2">
|
||||
<div class="d-flex gap-2">
|
||||
<select id="customerSelect" class="form-select form-select-sm shadow-none">
|
||||
<option value="">{% trans "Walking Customer" %}</option>
|
||||
{% for customer in customers %}
|
||||
<option value="{{ customer.id }}">{{ customer.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline-primary shadow-none" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
|
||||
<i class="bi bi-person-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body px-4 py-3 cart-items">
|
||||
<div id="cartItemsList">
|
||||
<!-- Cart items will be injected here -->
|
||||
</div>
|
||||
@ -168,10 +192,17 @@
|
||||
|
||||
<div class="card-footer bg-light border-0 p-4 rounded-bottom-4">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>{% trans "Subtotal" %}</span>
|
||||
<span id="subtotalAmount">{{ site_settings.currency_symbol }}0.000</span>
|
||||
<span class="small">{% trans "Subtotal" %}</span>
|
||||
<span id="subtotalAmount" class="small">{{ site_settings.currency_symbol }}0.000</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="small">{% trans "Discount" %}</span>
|
||||
<div class="input-group input-group-sm w-50">
|
||||
<input type="number" id="discountInput" class="form-control text-end shadow-none" value="0" min="0" step="0.001" onchange="updateTotals()">
|
||||
<span class="input-group-text bg-white border-start-0 small">{{ site_settings.currency_symbol }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-3 pt-2 border-top">
|
||||
<span class="fw-bold fs-5">{% trans "Total" %}</span>
|
||||
<span class="fw-bold fs-5 text-primary" id="totalAmount">{{ site_settings.currency_symbol }}0.000</span>
|
||||
</div>
|
||||
@ -185,7 +216,7 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button id="payNowBtn" class="btn btn-primary w-100 py-3 fw-bold rounded-3" onclick="checkout()" disabled>
|
||||
<button id="payNowBtn" class="btn btn-primary w-100 py-3 fw-bold rounded-3 shadow-none" onclick="checkout()" disabled>
|
||||
{% trans "PAY NOW" %}
|
||||
</button>
|
||||
</div>
|
||||
@ -194,6 +225,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Customer Modal -->
|
||||
<div class="modal fade no-print" id="addCustomerModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0 shadow rounded-4">
|
||||
<div class="modal-header border-0 pb-0 px-4 pt-4">
|
||||
<h5 class="fw-bold">{% trans "Add New Customer" %}</h5>
|
||||
<button type="button" class="btn-close shadow-none" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body px-4">
|
||||
<form id="addCustomerForm">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Full Name" %} *</label>
|
||||
<input type="text" name="name" class="form-control shadow-none" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" name="phone" class="form-control shadow-none">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Email" %}</label>
|
||||
<input type="email" name="email" class="form-control shadow-none">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer border-0 px-4 pb-4">
|
||||
<button type="button" class="btn btn-light rounded-3 px-4" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||
<button type="button" class="btn btn-primary rounded-3 px-4" onclick="submitQuickCustomer()">{% trans "Save Customer" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Invoice Template (Hidden) -->
|
||||
<div id="invoice-print">
|
||||
<div class="invoice-header">
|
||||
@ -252,16 +315,16 @@
|
||||
<!-- Receipt Modal -->
|
||||
<div class="modal fade no-print" id="receiptModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content border-0 shadow rounded-4">
|
||||
<div class="modal-body text-center p-4">
|
||||
<i class="bi bi-check-circle-fill text-success display-1 mb-3"></i>
|
||||
<h4 class="fw-bold">{% trans "Success!" %}</h4>
|
||||
<p class="text-muted">{% trans "Transaction completed." %}</p>
|
||||
<div class="d-grid gap-2">
|
||||
<button type="button" class="btn btn-primary" onclick="printInvoice()">
|
||||
<button type="button" class="btn btn-primary rounded-3" onclick="printInvoice()">
|
||||
<i class="bi bi-printer me-2"></i> {% trans "Print Invoice" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-outline-secondary rounded-3" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -275,7 +338,7 @@
|
||||
let lastSaleData = null;
|
||||
const lang = '{{ LANGUAGE_CODE }}';
|
||||
const currency = '{{ site_settings.currency_symbol }}';
|
||||
const decimalPlaces = currency === 'OMR' ? 3 : 2;
|
||||
const decimalPlaces = {{ decimal_places|default:3 }};
|
||||
|
||||
function formatAmount(amount) {
|
||||
return parseFloat(amount).toFixed(decimalPlaces);
|
||||
@ -313,6 +376,24 @@
|
||||
renderCart();
|
||||
}
|
||||
|
||||
function clearCart() {
|
||||
if (cart.length === 0) return;
|
||||
if (confirm('{% trans "Are you sure you want to clear the current order?" %}')) {
|
||||
cart = [];
|
||||
document.getElementById('discountInput').value = 0;
|
||||
renderCart();
|
||||
}
|
||||
}
|
||||
|
||||
function updateTotals() {
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const total = Math.max(0, subtotal - discount);
|
||||
|
||||
document.getElementById('subtotalAmount').innerText = `${currency} ${formatAmount(subtotal)}`;
|
||||
document.getElementById('totalAmount').innerText = `${currency} ${formatAmount(total)}`;
|
||||
}
|
||||
|
||||
function renderCart() {
|
||||
const listContainer = document.getElementById('cartItemsList');
|
||||
const emptyMsg = document.getElementById('emptyCartMsg');
|
||||
@ -321,8 +402,7 @@
|
||||
if (cart.length === 0) {
|
||||
emptyMsg.classList.remove('d-none');
|
||||
listContainer.innerHTML = '';
|
||||
document.getElementById('subtotalAmount').innerText = `${currency} ${formatAmount(0)}`;
|
||||
document.getElementById('totalAmount').innerText = `${currency} ${formatAmount(0)}`;
|
||||
updateTotals();
|
||||
payBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
@ -330,28 +410,25 @@
|
||||
emptyMsg.classList.add('d-none');
|
||||
payBtn.disabled = false;
|
||||
let html = '';
|
||||
let total = 0;
|
||||
|
||||
cart.forEach(item => {
|
||||
total += item.line_total;
|
||||
html += `
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<div style="flex: 1;">
|
||||
<div class="fw-bold small">${item.name}</div>
|
||||
<div class="text-muted small">${currency} ${formatAmount(item.price)} x ${item.quantity}</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-circle" onclick="updateQuantity(${item.id}, -1)">-</button>
|
||||
<span class="fw-bold">${item.quantity}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-circle" onclick="updateQuantity(${item.id}, 1)">+</button>
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-circle" style="width:24px; height:24px; padding:0; display:flex; align-items:center; justify-content:center;" onclick="updateQuantity(${item.id}, -1)">-</button>
|
||||
<span class="fw-bold small">${item.quantity}</span>
|
||||
<button class="btn btn-sm btn-outline-secondary rounded-circle" style="width:24px; height:24px; padding:0; display:flex; align-items:center; justify-content:center;" onclick="updateQuantity(${item.id}, 1)">+</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
listContainer.innerHTML = html;
|
||||
document.getElementById('subtotalAmount').innerText = `${currency} ${formatAmount(total)}`;
|
||||
document.getElementById('totalAmount').innerText = `${currency} ${formatAmount(total)}`;
|
||||
updateTotals();
|
||||
}
|
||||
|
||||
function checkout() {
|
||||
@ -362,14 +439,17 @@
|
||||
payBtn.disabled = true;
|
||||
payBtn.innerText = '{% trans "Processing..." %}';
|
||||
|
||||
const totalAmount = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const subtotal = cart.reduce((acc, item) => acc + item.line_total, 0);
|
||||
const discount = parseFloat(document.getElementById('discountInput').value) || 0;
|
||||
const totalAmount = Math.max(0, subtotal - discount);
|
||||
|
||||
const data = {
|
||||
customer_id: document.getElementById('customerSelect').value,
|
||||
payment_method_id: document.getElementById('paymentMethodSelect').value,
|
||||
items: cart,
|
||||
total_amount: totalAmount,
|
||||
paid_amount: totalAmount,
|
||||
discount: 0,
|
||||
discount: discount,
|
||||
payment_type: 'cash'
|
||||
};
|
||||
|
||||
@ -392,6 +472,7 @@
|
||||
lastSaleData = data;
|
||||
prepareInvoice(data);
|
||||
cart = [];
|
||||
document.getElementById('discountInput').value = 0;
|
||||
renderCart();
|
||||
const receiptModal = new bootstrap.Modal(document.getElementById('receiptModal'));
|
||||
receiptModal.show();
|
||||
@ -410,8 +491,6 @@
|
||||
}
|
||||
|
||||
function prepareInvoice(data) {
|
||||
console.log('Preparing invoice with data:', data);
|
||||
|
||||
const logo = document.getElementById('inv-logo');
|
||||
if (data.business.logo_url) {
|
||||
logo.src = data.business.logo_url;
|
||||
@ -450,10 +529,45 @@
|
||||
}
|
||||
|
||||
function printInvoice() {
|
||||
console.log('Printing invoice...');
|
||||
window.print();
|
||||
}
|
||||
|
||||
function submitQuickCustomer() {
|
||||
const form = document.getElementById('addCustomerForm');
|
||||
const formData = new FormData(form);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
if (!data.name) {
|
||||
alert('{% trans "Customer name is required" %}');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('{% url "add_customer_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const select = document.getElementById('customerSelect');
|
||||
const option = new Option(data.customer.name, data.customer.id, true, true);
|
||||
select.add(option);
|
||||
|
||||
// Close modal
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('addCustomerModal'));
|
||||
modal.hide();
|
||||
form.reset();
|
||||
} else {
|
||||
alert(data.error || 'Error adding customer');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
|
||||
// Search and Category Filtering
|
||||
document.getElementById('productSearch').addEventListener('input', filterProducts);
|
||||
document.querySelectorAll('.category-badge').forEach(badge => {
|
||||
@ -484,4 +598,4 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -47,7 +47,7 @@
|
||||
<h5 class="card-title mb-0 fw-bold">{% trans "Business Profile" %}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
<form method="post" enctype="multipart/form-data" action="{% url 'settings' %}">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12 text-center mb-3">
|
||||
@ -90,15 +90,20 @@
|
||||
<hr class="my-4">
|
||||
|
||||
<h5 class="fw-bold mb-3">{% trans "Financial Preferences" %}</h5>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">{% trans "Currency Symbol" %}</label>
|
||||
<input type="text" name="currency_symbol" class="form-control" value="{{ settings.currency_symbol }}" required>
|
||||
<div class="form-text">{% trans "e.g., OMR, $, £, SAR" %}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">{% trans "Default Tax Rate (%)" %}</label>
|
||||
<input type="number" step="0.01" name="tax_rate" class="form-control" value="{{ settings.tax_rate }}" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-semibold">{% trans "Decimal Places" %}</label>
|
||||
<input type="number" name="decimal_places" class="form-control" value="{{ settings.decimal_places }}" min="0" max="5" required>
|
||||
<div class="form-text">{% trans "For price display" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-3 border-top d-flex justify-content-end">
|
||||
@ -148,7 +153,7 @@
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<table class="table table-hover align-middle mb-0" id="paymentMethodsTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Name (EN)" %}</th>
|
||||
@ -159,7 +164,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for pm in payment_methods %}
|
||||
<tr>
|
||||
<tr id="pm-row-{{ pm.id }}">
|
||||
<td class="ps-4 fw-semibold">{{ pm.name_en }}</td>
|
||||
<td>{{ pm.name_ar }}</td>
|
||||
<td>
|
||||
@ -236,7 +241,7 @@
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5">
|
||||
<td colspan="4" class="text-center py-5" id="noPaymentMethods">
|
||||
<div class="text-muted">
|
||||
<i class="bi bi-credit-card fs-1 d-block mb-3"></i>
|
||||
{% trans "No payment methods found." %}
|
||||
@ -256,39 +261,37 @@
|
||||
<!-- Add Payment Method Modal -->
|
||||
<div class="modal fade" id="addPaymentModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-0">
|
||||
<form action="{% url 'add_payment_method' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold">{% trans "Add Payment Method" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
<div class="modal-content border-0 shadow">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fw-bold">{% trans "Add Payment Method" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" id="addPmNameEn" class="form-control" required placeholder="e.g. Bank Transfer">
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">{% trans "Name (English)" %}</label>
|
||||
<input type="text" name="name_en" class="form-control" required placeholder="e.g. Bank Transfer">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" name="name_ar" class="form-control" required placeholder="e.g. تحويل بنكي">
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="is_active" checked>
|
||||
<label class="form-check-label">{% trans "Active" %}</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">{% trans "Name (Arabic)" %}</label>
|
||||
<input type="text" id="addPmNameAr" class="form-control" required placeholder="e.g. تحويل بنكي">
|
||||
</div>
|
||||
<div class="modal-footer bg-light border-0">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Create Method" %}</button>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="addPmIsActive" checked>
|
||||
<label class="form-check-label">{% trans "Active" %}</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer bg-light border-0">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="saveAndAddAnotherPm">{% trans "Save & Add Another" %}</button>
|
||||
<button type="button" class="btn btn-primary" id="savePm">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Persist active tab on reload
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@ -307,6 +310,77 @@
|
||||
window.location.hash = event.target.getAttribute('data-bs-target');
|
||||
});
|
||||
});
|
||||
|
||||
// AJAX for Payment Method
|
||||
async function savePaymentMethod(stayOpen = false) {
|
||||
const nameEn = document.getElementById('addPmNameEn').value;
|
||||
const nameAr = document.getElementById('addPmNameAr').value;
|
||||
const isActive = document.getElementById('addPmIsActive').checked;
|
||||
|
||||
if (!nameEn || !nameAr) {
|
||||
alert('{% trans "Please fill all required fields" %}');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('{% url "add_payment_method_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name_en: nameEn,
|
||||
name_ar: nameAr,
|
||||
is_active: isActive
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
// Refresh table or append row (for simplicity, we suggest a reload or partial update)
|
||||
// For a smooth experience, let's append the row
|
||||
const tableBody = document.querySelector('#paymentMethodsTable tbody');
|
||||
const noItems = document.getElementById('noPaymentMethods');
|
||||
if (noItems) noItems.parentElement.remove();
|
||||
|
||||
const newRow = document.createElement('tr');
|
||||
newRow.id = 'pm-row-' + data.id;
|
||||
newRow.innerHTML = `
|
||||
<td class="ps-4 fw-semibold">${data.name_en}</td>
|
||||
<td>${data.name_ar}</td>
|
||||
<td>
|
||||
<span class="badge ${isActive ? 'bg-success-soft text-success' : 'bg-danger-soft text-danger'}">
|
||||
${isActive ? '{% trans "Active" %}' : '{% trans "Inactive" %}'}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<small class="text-muted">{% trans "Reload to edit" %}</small>
|
||||
</td>
|
||||
`;
|
||||
tableBody.appendChild(newRow);
|
||||
|
||||
// Success Toast/Alert (Simplified)
|
||||
alert('{% trans "Payment method added successfully!" %}');
|
||||
|
||||
if (stayOpen) {
|
||||
// Clear fields
|
||||
document.getElementById('addPmNameEn').value = '';
|
||||
document.getElementById('addPmNameAr').value = '';
|
||||
} else {
|
||||
bootstrap.Modal.getInstance(document.getElementById('addPaymentModal')).hide();
|
||||
window.location.reload(); // Reload to get modals for the new row
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('{% trans "An error occurred. Please try again." %}');
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('savePm').addEventListener('click', () => savePaymentMethod(false));
|
||||
document.getElementById('saveAndAddAnotherPm').addEventListener('click', () => savePaymentMethod(true));
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Suppliers" %} | Meezan Accounting{% endblock %}
|
||||
{% block title %}{% trans "Suppliers" %} | {{ site_settings.business_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
@ -16,7 +16,7 @@
|
||||
</nav>
|
||||
</div>
|
||||
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addSupplierModal">
|
||||
<i class="bi bi-truck me-2"></i>{% trans "Add Supplier" %}
|
||||
<i class="bi bi-person-plus me-2"></i>{% trans "Add Supplier" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -34,10 +34,10 @@
|
||||
<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">
|
||||
<table class="table table-hover align-middle mb-0" id="suppliersTable">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Supplier" %}</th>
|
||||
<th class="ps-4">{% trans "Name" %}</th>
|
||||
<th>{% trans "Contact Person" %}</th>
|
||||
<th>{% trans "Phone" %}</th>
|
||||
<th class="text-end pe-4">{% trans "Actions" %}</th>
|
||||
@ -51,9 +51,6 @@
|
||||
<td>{{ supplier.phone|default:"-" }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#viewSupplierModal{{ supplier.id }}" title="{% trans 'View' %}">
|
||||
<i class="bi bi-eye text-primary"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-light rounded-pill px-2 me-1" data-bs-toggle="modal" data-bs-target="#editSupplierModal{{ supplier.id }}" title="{% trans 'Edit' %}">
|
||||
<i class="bi bi-pencil text-success"></i>
|
||||
</button>
|
||||
@ -63,77 +60,6 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- View Supplier Modal -->
|
||||
<div class="modal fade" id="viewSupplierModal{{ supplier.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Supplier Details" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-center mb-4">
|
||||
<div class="bg-primary bg-opacity-10 text-primary rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 80px; height: 80px;">
|
||||
<i class="bi bi-truck" style="font-size: 2.5rem;"></i>
|
||||
</div>
|
||||
<h4 class="fw-bold mb-0">{{ supplier.name }}</h4>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="row g-3">
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Contact Person" %}</small>
|
||||
<span class="fw-bold">{{ supplier.contact_person|default:"-" }}</span>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Phone" %}</small>
|
||||
<span class="fw-bold">{{ supplier.phone|default:"-" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Supplier Modal -->
|
||||
<div class="modal fade" id="editSupplierModal{{ supplier.id }}" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content rounded-4 border-0 shadow">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit Supplier" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'edit_supplier' supplier.id %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Supplier Name" %}</label>
|
||||
<input type="text" name="name" class="form-control rounded-3" value="{{ supplier.name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Contact Person" %}</label>
|
||||
<input type="text" name="contact_person" class="form-control rounded-3" value="{{ supplier.contact_person }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" name="phone" class="form-control rounded-3" value="{{ supplier.phone }}">
|
||||
</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 "Update Supplier" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5">
|
||||
<i class="bi bi-truck text-muted opacity-25" style="font-size: 3rem;"></i>
|
||||
<p class="mt-2 text-muted">{% trans "No suppliers found." %}</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -150,28 +76,62 @@
|
||||
<h5 class="modal-title fw-bold">{% trans "Add New Supplier" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form action="{% url 'add_supplier' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Supplier Name" %}</label>
|
||||
<input type="text" name="name" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Contact Person" %}</label>
|
||||
<input type="text" name="contact_person" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" name="phone" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Supplier Name" %}</label>
|
||||
<input type="text" id="addSuppName" class="form-control rounded-3" required>
|
||||
</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 Supplier" %}</button>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Contact Person" %}</label>
|
||||
<input type="text" id="addSuppContact" class="form-control rounded-3">
|
||||
</div>
|
||||
</form>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Phone Number" %}</label>
|
||||
<input type="text" id="addSuppPhone" class="form-control rounded-3">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-light rounded-3" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="button" class="btn btn-outline-primary rounded-3" id="saveAndAddAnotherSupp">{% trans "Save & Add Another" %}</button>
|
||||
<button type="button" class="btn btn-primary rounded-3 px-4" id="saveSupp">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
async function saveSupplier(stayOpen = false) {
|
||||
const name = document.getElementById('addSuppName').value;
|
||||
const contact_person = document.getElementById('addSuppContact').value;
|
||||
const phone = document.getElementById('addSuppPhone').value;
|
||||
|
||||
if (!name) return alert('Please fill supplier name');
|
||||
|
||||
const response = await fetch('{% url "add_supplier_ajax" %}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name, contact_person, phone })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
if (stayOpen) {
|
||||
document.getElementById('addSuppName').value = '';
|
||||
document.getElementById('addSuppContact').value = '';
|
||||
document.getElementById('addSuppPhone').value = '';
|
||||
alert('{% trans "Supplier added. You can add another one." %}');
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + data.error);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('saveSupp').onclick = () => saveSupplier(false);
|
||||
document.getElementById('saveAndAddAnotherSupp').onclick = () => saveSupplier(true);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,75 +1,160 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "User Management" %} - {{ site_settings.business_name }}{% endblock %}
|
||||
{% block title %}{% trans "User & Role Management" %} - {{ site_settings.business_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="h4 mb-0 fw-bold">{% trans "User Management" %}</h2>
|
||||
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="bi bi-person-plus me-2"></i> {% trans "Add New User" %}
|
||||
</button>
|
||||
<h2 class="h4 mb-0 fw-bold">{% trans "User & Role Management" %}</h2>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-outline-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addGroupModal">
|
||||
<i class="bi bi-shield-lock me-2"></i> {% trans "Add New Group" %}
|
||||
</button>
|
||||
<button class="btn btn-primary shadow-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
|
||||
<i class="bi bi-person-plus me-2"></i> {% trans "Add New User" %}
|
||||
</button>
|
||||
</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 text-muted small text-uppercase fw-bold">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Username" %}</th>
|
||||
<th>{% trans "Email" %}</th>
|
||||
<th>{% trans "Role/Group" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Last Login" %}</th>
|
||||
<th class="text-end pe-4">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-primary-subtle text-primary rounded-circle p-2 me-3 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="bi bi-person fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{{ u.username }}</div>
|
||||
{% if u.is_superuser %}<span class="badge bg-danger-subtle text-danger small">Superuser</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ u.email|default:"-" }}</td>
|
||||
<td>
|
||||
{% for group in u.groups.all %}
|
||||
<span class="badge bg-info-subtle text-info">{{ group.name }}</span>
|
||||
{% empty %}
|
||||
<span class="text-muted small">No Role</span>
|
||||
<!-- Custom Nav Tabs -->
|
||||
<ul class="nav nav-pills mb-4 gap-2" id="managementTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active rounded-pill px-4 fw-bold shadow-sm" id="users-tab" data-bs-toggle="pill" data-bs-target="#users" type="button" role="tab">
|
||||
<i class="bi bi-people me-2"></i> {% trans "Users" %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link rounded-pill px-4 fw-bold shadow-sm" id="groups-tab" data-bs-toggle="pill" data-bs-target="#groups" type="button" role="tab">
|
||||
<i class="bi bi-shield-check me-2"></i> {% trans "Groups & Permissions" %}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="managementTabsContent">
|
||||
<!-- Users Tab -->
|
||||
<div class="tab-pane fade show active" id="users" role="tabpanel">
|
||||
<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 text-muted small text-uppercase fw-bold">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "User" %}</th>
|
||||
<th>{% trans "Email" %}</th>
|
||||
<th>{% trans "Groups" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Last Login" %}</th>
|
||||
<th class="text-end pe-4">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for u in users %}
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-primary-subtle text-primary rounded-circle p-2 me-3 d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
|
||||
<i class="bi bi-person fs-5"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{{ u.username }}</div>
|
||||
{% if u.is_superuser %}<span class="badge bg-danger-subtle text-danger small">Superuser</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ u.email|default:"-" }}</td>
|
||||
<td>
|
||||
{% for group in u.groups.all %}
|
||||
<span class="badge bg-info-subtle text-info">{{ group.name }}</span>
|
||||
{% empty %}
|
||||
<span class="text-muted small">No Role</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.is_active %}
|
||||
<span class="badge bg-success-subtle text-success">{% trans "Active" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary-subtle text-secondary">{% trans "Inactive" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ u.last_login|date:"Y-m-d H:i"|default:"Never" }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary edit-user-btn"
|
||||
data-id="{{ u.id }}"
|
||||
data-username="{{ u.username }}"
|
||||
data-email="{{ u.email }}"
|
||||
data-groups='[{% for g in u.groups.all %}"{{ g.id }}"{% if not forloop.last %},{% endif %}{% endfor %}]'
|
||||
data-bs-toggle="modal" data-bs-target="#editUserModal">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<form action="{% url 'user_management' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="toggle_status">
|
||||
<input type="hidden" name="user_id" value="{{ u.id }}">
|
||||
<button type="submit" class="btn btn-sm {% if u.is_active %}btn-outline-warning{% else %}btn-outline-success{% endif %}"
|
||||
{% if u == user %}disabled title="Cannot deactivate yourself"{% endif %}>
|
||||
{% if u.is_active %}<i class="bi bi-person-x"></i>{% else %}<i class="bi bi-person-check"></i>{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>
|
||||
{% if u.is_active %}
|
||||
<span class="badge bg-success-subtle text-success">{% trans "Active" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary-subtle text-secondary">{% trans "Inactive" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ u.last_login|date:"Y-m-d H:i"|default:"Never" }}</td>
|
||||
<td class="text-end pe-4">
|
||||
<form action="{% url 'user_management' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="toggle_status">
|
||||
<input type="hidden" name="user_id" value="{{ u.id }}">
|
||||
<button type="submit" class="btn btn-sm {% if u.is_active %}btn-outline-warning{% else %}btn-outline-success{% endif %}"
|
||||
{% if u == user %}disabled title="Cannot deactivate yourself"{% endif %}>
|
||||
{% if u.is_active %}<i class="bi bi-person-x"></i>{% else %}<i class="bi bi-person-check"></i>{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Groups Tab -->
|
||||
<div class="tab-pane fade" id="groups" role="tabpanel">
|
||||
<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 text-muted small text-uppercase fw-bold">
|
||||
<tr>
|
||||
<th class="ps-4">{% trans "Group Name" %}</th>
|
||||
<th>{% trans "Permissions Count" %}</th>
|
||||
<th>{% trans "Users Count" %}</th>
|
||||
<th class="text-end pe-4">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for g in groups %}
|
||||
<tr>
|
||||
<td class="ps-4 fw-bold text-primary">{{ g.name }}</td>
|
||||
<td>
|
||||
<span class="badge bg-secondary-subtle text-secondary">{{ g.permissions.count }} {% trans "Permissions" %}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info-subtle text-info">{{ g.user_set.count }} {% trans "Users" %}</span>
|
||||
</td>
|
||||
<td class="text-end pe-4">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm btn-outline-primary edit-group-btn"
|
||||
data-id="{{ g.id }}"
|
||||
data-name="{{ g.name }}"
|
||||
data-bs-toggle="modal" data-bs-target="#editGroupModal">
|
||||
<i class="bi bi-shield-lock"></i> Edit Perms
|
||||
</button>
|
||||
<form action="{% url 'user_management' %}" method="post" class="d-inline" onsubmit="return confirm('{% trans "Are you sure you want to delete this group?" %}');">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_group">
|
||||
<input type="hidden" name="group_id" value="{{ g.id }}">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -99,13 +184,13 @@
|
||||
<input type="password" name="password" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Role/Group" %}</label>
|
||||
<select name="group" class="form-select rounded-3">
|
||||
<option value="">{% trans "No Group (General Staff)" %}</option>
|
||||
<label class="form-label small fw-bold">{% trans "Assign Groups" %}</label>
|
||||
<select name="groups" class="form-select rounded-3" multiple style="height: 120px;">
|
||||
{% for g in groups %}
|
||||
<option value="{{ g.id }}">{{ g.name|title }}</option>
|
||||
<option value="{{ g.id }}">{{ g.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<div class="form-text small text-muted">Hold Ctrl/Cmd to select multiple groups.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-0">
|
||||
@ -116,4 +201,193 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit User Modal -->
|
||||
<div class="modal fade" id="editUserModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit User" %}: <span id="editUserTitle"></span></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="{% url 'user_management' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="edit_user">
|
||||
<input type="hidden" name="user_id" id="editUserId">
|
||||
<div class="modal-body py-4">
|
||||
<div class="mb-3 text-muted small">Username: <strong id="editUserUsername"></strong></div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Email" %}</label>
|
||||
<input type="email" name="email" id="editUserEmail" class="form-control rounded-3">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "New Password" %}</label>
|
||||
<input type="password" name="password" class="form-control rounded-3" placeholder="Leave blank to keep current">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold">{% trans "Assign Groups" %}</label>
|
||||
<select name="groups" id="editUserGroups" class="form-select rounded-3" multiple style="height: 120px;">
|
||||
{% for g in groups %}
|
||||
<option value="{{ g.id }}">{{ g.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-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 Changes" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Group Modal -->
|
||||
<div class="modal fade" id="addGroupModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Create New Role/Group" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="{% url 'user_management' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_group">
|
||||
<div class="modal-body py-4">
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold">{% trans "Group Name" %}</label>
|
||||
<input type="text" name="name" class="form-control rounded-3" required placeholder="e.g., Inventory Manager">
|
||||
</div>
|
||||
<h6 class="fw-bold mb-3 small text-uppercase text-muted">{% trans "Assign Permissions" %}</h6>
|
||||
<div class="row g-3" style="max-height: 400px; overflow-y: auto;">
|
||||
{% for perm in permissions %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="form-check card p-2 border-light shadow-none">
|
||||
<input class="form-check-input ms-0 me-2" type="checkbox" name="permissions" value="{{ perm.id }}" id="perm_{{ perm.id }}">
|
||||
<label class="form-check-label small d-block" for="perm_{{ perm.id }}">
|
||||
<span class="text-primary fw-bold">{{ perm.content_type.app_label }}</span> | {{ perm.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-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 "Create Group" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Group Modal -->
|
||||
<div class="modal fade" id="editGroupModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
||||
<div class="modal-content border-0 shadow-lg rounded-4">
|
||||
<div class="modal-header border-0 pb-0">
|
||||
<h5 class="modal-title fw-bold">{% trans "Edit Role/Group" %}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form action="{% url 'user_management' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="edit_group">
|
||||
<input type="hidden" name="group_id" id="editGroupId">
|
||||
<div class="modal-body py-4">
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold">{% trans "Group Name" %}</label>
|
||||
<input type="text" name="name" id="editGroupName" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<h6 class="fw-bold mb-3 small text-uppercase text-muted">{% trans "Permissions" %}</h6>
|
||||
<div class="row g-3" style="max-height: 400px; overflow-y: auto;">
|
||||
{% for perm in permissions %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="form-check card p-2 border-light shadow-none">
|
||||
<input class="form-check-input edit-group-perm ms-0 me-2" type="checkbox" name="permissions" value="{{ perm.id }}" id="edit_perm_{{ perm.id }}">
|
||||
<label class="form-check-label small d-block" for="edit_perm_{{ perm.id }}">
|
||||
<span class="text-primary fw-bold">{{ perm.content_type.app_label }}</span> | {{ perm.name }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-0 pt-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 Changes" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Tab Persistence
|
||||
const hash = window.location.hash;
|
||||
if (hash) {
|
||||
const triggerEl = document.querySelector('#managementTabs button[data-bs-target="' + hash + '"]');
|
||||
if (triggerEl) {
|
||||
bootstrap.Tab.getOrCreateInstance(triggerEl).show();
|
||||
}
|
||||
}
|
||||
|
||||
const tabButtons = document.querySelectorAll('#managementTabs button');
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('shown.bs.tab', event => {
|
||||
window.location.hash = event.target.getAttribute('data-bs-target');
|
||||
});
|
||||
});
|
||||
|
||||
// Edit User Modal Population
|
||||
const editUserBtns = document.querySelectorAll('.edit-user-btn');
|
||||
editUserBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const id = this.dataset.id;
|
||||
const username = this.dataset.username;
|
||||
const email = this.dataset.email;
|
||||
const groups = JSON.parse(this.dataset.groups);
|
||||
|
||||
document.getElementById('editUserId').value = id;
|
||||
document.getElementById('editUserTitle').innerText = username;
|
||||
document.getElementById('editUserUsername').innerText = username;
|
||||
document.getElementById('editUserEmail').value = email;
|
||||
|
||||
const groupSelect = document.getElementById('editUserGroups');
|
||||
Array.from(groupSelect.options).forEach(option => {
|
||||
option.selected = groups.includes(option.value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Edit Group Modal Population
|
||||
const editGroupBtns = document.querySelectorAll('.edit-group-btn');
|
||||
editGroupBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const id = this.dataset.id;
|
||||
const name = this.dataset.name;
|
||||
|
||||
document.getElementById('editGroupId').value = id;
|
||||
document.getElementById('editGroupName').value = name;
|
||||
|
||||
// Reset checkboxes
|
||||
document.querySelectorAll('.edit-group-perm').forEach(cb => cb.checked = false);
|
||||
|
||||
// Fetch group permissions
|
||||
fetch(`/api/group-details/${id}/`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.permissions) {
|
||||
data.permissions.forEach(permId => {
|
||||
const cb = document.getElementById(`edit_perm_${permId}`);
|
||||
if (cb) cb.checked = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -55,6 +55,7 @@ urlpatterns = [
|
||||
path('customers/add/', views.add_customer, name='add_customer'),
|
||||
path('customers/edit/<int:pk>/', views.edit_customer, name='edit_customer'),
|
||||
path('customers/delete/<int:pk>/', views.delete_customer, name='delete_customer'),
|
||||
path('api/add-customer-ajax/', views.add_customer_ajax, name='add_customer_ajax'),
|
||||
|
||||
# Suppliers
|
||||
path('suppliers/add/', views.add_supplier, name='add_supplier'),
|
||||
@ -86,4 +87,5 @@ urlpatterns = [
|
||||
path('settings/payment-methods/add/', views.add_payment_method, name='add_payment_method'),
|
||||
path('settings/payment-methods/edit/<int:pk>/', views.edit_payment_method, name='edit_payment_method'),
|
||||
path('settings/payment-methods/delete/<int:pk>/', views.delete_payment_method, name='delete_payment_method'),
|
||||
]
|
||||
path('api/add-payment-method-ajax/', views.add_payment_method_ajax, name='add_payment_method_ajax'),
|
||||
]
|
||||
141
core/views.py
141
core/views.py
@ -1,3 +1,4 @@
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
from django.urls import reverse
|
||||
import random
|
||||
import string
|
||||
@ -74,10 +75,12 @@ def inventory(request):
|
||||
products = Product.objects.all().select_related('category', 'unit', 'supplier')
|
||||
categories = Category.objects.all()
|
||||
suppliers = Supplier.objects.all()
|
||||
units = Unit.objects.all()
|
||||
context = {
|
||||
'products': products,
|
||||
'categories': categories,
|
||||
'suppliers': suppliers
|
||||
'suppliers': suppliers,
|
||||
'units': units
|
||||
}
|
||||
return render(request, 'core/inventory.html', context)
|
||||
|
||||
@ -640,7 +643,7 @@ def purchase_return_create(request):
|
||||
purchases = Purchase.objects.all().order_by('-created_at')
|
||||
return render(request, 'core/purchase_return_create.html', {
|
||||
'products': products,
|
||||
'suppliers': suppliers,
|
||||
'customers': suppliers,
|
||||
'purchases': purchases
|
||||
})
|
||||
|
||||
@ -748,6 +751,7 @@ def settings_view(request):
|
||||
settings.email = request.POST.get('email')
|
||||
settings.currency_symbol = request.POST.get('currency_symbol')
|
||||
settings.tax_rate = request.POST.get('tax_rate')
|
||||
settings.decimal_places = request.POST.get('decimal_places', 3)
|
||||
settings.vat_number = request.POST.get('vat_number')
|
||||
settings.registration_number = request.POST.get('registration_number')
|
||||
|
||||
@ -756,7 +760,7 @@ def settings_view(request):
|
||||
|
||||
settings.save()
|
||||
messages.success(request, "Settings updated successfully!")
|
||||
return redirect('settings')
|
||||
return redirect(reverse('settings') + '#profile')
|
||||
|
||||
payment_methods = PaymentMethod.objects.all()
|
||||
|
||||
@ -773,7 +777,7 @@ def add_payment_method(request):
|
||||
is_active = request.POST.get('is_active') == 'on'
|
||||
PaymentMethod.objects.create(name_en=name_en, name_ar=name_ar, is_active=is_active)
|
||||
messages.success(request, "Payment method added successfully!")
|
||||
return redirect('settings')
|
||||
return redirect(reverse('settings') + '#payments')
|
||||
|
||||
@login_required
|
||||
def edit_payment_method(request, pk):
|
||||
@ -784,14 +788,14 @@ def edit_payment_method(request, pk):
|
||||
pm.is_active = request.POST.get('is_active') == 'on'
|
||||
pm.save()
|
||||
messages.success(request, "Payment method updated successfully!")
|
||||
return redirect('settings')
|
||||
return redirect(reverse('settings') + '#payments')
|
||||
|
||||
@login_required
|
||||
def delete_payment_method(request, pk):
|
||||
pm = get_object_or_404(PaymentMethod, pk=pk)
|
||||
pm.delete()
|
||||
messages.success(request, "Payment method deleted successfully!")
|
||||
return redirect('settings')
|
||||
return redirect(reverse('settings') + '#payments')
|
||||
|
||||
@login_required
|
||||
def add_customer(request):
|
||||
@ -1165,9 +1169,10 @@ def user_management(request):
|
||||
messages.error(request, "Access denied.")
|
||||
return redirect('index')
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
users = User.objects.all().prefetch_related('groups')
|
||||
groups = Group.objects.all()
|
||||
groups = Group.objects.all().prefetch_related('permissions')
|
||||
# Filter for relevant permissions (core and auth)
|
||||
permissions = Permission.objects.select_related('content_type').all().order_by('content_type__app_label', 'codename')
|
||||
|
||||
if request.method == 'POST':
|
||||
action = request.POST.get('action')
|
||||
@ -1175,19 +1180,62 @@ def user_management(request):
|
||||
username = request.POST.get('username')
|
||||
password = request.POST.get('password')
|
||||
email = request.POST.get('email')
|
||||
group_id = request.POST.get('group')
|
||||
group_ids = request.POST.getlist('groups')
|
||||
|
||||
if User.objects.filter(username=username).exists():
|
||||
messages.error(request, "Username already exists.")
|
||||
else:
|
||||
user = User.objects.create_user(username=username, email=email, password=password)
|
||||
if group_id:
|
||||
group = Group.objects.get(id=group_id)
|
||||
user.groups.add(group)
|
||||
if group_ids:
|
||||
selected_groups = Group.objects.filter(id__in=group_ids)
|
||||
user.groups.set(selected_groups)
|
||||
user.is_staff = True
|
||||
user.save()
|
||||
messages.success(request, f"User {username} created successfully.")
|
||||
|
||||
elif action == 'edit_user':
|
||||
user_id = request.POST.get('user_id')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
user.email = request.POST.get('email')
|
||||
group_ids = request.POST.getlist('groups')
|
||||
selected_groups = Group.objects.filter(id__in=group_ids)
|
||||
user.groups.set(selected_groups)
|
||||
|
||||
password = request.POST.get('password')
|
||||
if password:
|
||||
user.set_password(password)
|
||||
|
||||
user.save()
|
||||
messages.success(request, f"User {user.username} updated.")
|
||||
|
||||
elif action == 'add_group':
|
||||
name = request.POST.get('name')
|
||||
permission_ids = request.POST.getlist('permissions')
|
||||
if Group.objects.filter(name=name).exists():
|
||||
messages.error(request, "Group name already exists.")
|
||||
else:
|
||||
group = Group.objects.create(name=name)
|
||||
if permission_ids:
|
||||
perms = Permission.objects.filter(id__in=permission_ids)
|
||||
group.permissions.set(perms)
|
||||
messages.success(request, f"Group {name} created successfully.")
|
||||
|
||||
elif action == 'edit_group':
|
||||
group_id = request.POST.get('group_id')
|
||||
group = get_object_or_404(Group, id=group_id)
|
||||
group.name = request.POST.get('name')
|
||||
permission_ids = request.POST.getlist('permissions')
|
||||
perms = Permission.objects.filter(id__in=permission_ids)
|
||||
group.permissions.set(perms)
|
||||
group.save()
|
||||
messages.success(request, f"Group {group.name} updated.")
|
||||
|
||||
elif action == 'delete_group':
|
||||
group_id = request.POST.get('group_id')
|
||||
group = get_object_or_404(Group, id=group_id)
|
||||
group.delete()
|
||||
messages.success(request, "Group deleted.")
|
||||
|
||||
elif action == 'toggle_status':
|
||||
user_id = request.POST.get('user_id')
|
||||
user = get_object_or_404(User, id=user_id)
|
||||
@ -1198,6 +1246,71 @@ def user_management(request):
|
||||
user.save()
|
||||
messages.success(request, f"User {user.username} status updated.")
|
||||
|
||||
return redirect('user_management')
|
||||
# Determine redirect hash based on action
|
||||
target_hash = ""
|
||||
if action in ['add_group', 'edit_group', 'delete_group']:
|
||||
target_hash = "#groups"
|
||||
|
||||
return render(request, 'core/users.html', {'users': users, 'groups': groups})
|
||||
return redirect(reverse('user_management') + target_hash)
|
||||
|
||||
return render(request, 'core/users.html', {
|
||||
'users': users,
|
||||
'groups': groups,
|
||||
'permissions': permissions
|
||||
})
|
||||
|
||||
@login_required
|
||||
def group_details_api(request, pk):
|
||||
group = get_object_or_404(Group, pk=pk)
|
||||
permissions = group.permissions.all().values_list('id', flat=True)
|
||||
return JsonResponse({
|
||||
'id': group.id,
|
||||
'name': group.name,
|
||||
'permissions': list(permissions)
|
||||
})
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
def add_payment_method_ajax(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
name_en = data.get('name_en')
|
||||
name_ar = data.get('name_ar')
|
||||
is_active = data.get('is_active', True)
|
||||
if not name_en or not name_ar:
|
||||
return JsonResponse({'success': False, 'error': 'Missing names'}, status=400)
|
||||
|
||||
pm = PaymentMethod.objects.create(name_en=name_en, name_ar=name_ar, is_active=is_active)
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'id': pm.id,
|
||||
'name_en': pm.name_en,
|
||||
'name_ar': pm.name_ar
|
||||
})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
def add_customer_ajax(request):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
name = data.get('name')
|
||||
phone = data.get('phone', '')
|
||||
email = data.get('email', '')
|
||||
address = data.get('address', '')
|
||||
if not name:
|
||||
return JsonResponse({'success': False, 'error': 'Missing name'}, status=400)
|
||||
|
||||
customer = Customer.objects.create(name=name, phone=phone, email=email, address=address)
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'id': customer.id,
|
||||
'name': customer.name
|
||||
})
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
||||
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user