improving pos

This commit is contained in:
Flatlogic Bot 2026-02-02 16:18:44 +00:00
parent ddd4aa0397
commit e9c5a5c213
16 changed files with 1037 additions and 937 deletions

View File

@ -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}

View 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'),
),
]

View File

@ -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

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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'),
]

View File

@ -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)