more modification
This commit is contained in:
parent
e405332220
commit
2a2b761270
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,3 +1,12 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
|
.env
|
||||||
|
db.sqlite3
|
||||||
|
media/
|
||||||
|
staticfiles/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
tmp/
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
17
core/migrations/0036_alter_systemsetting_options.py
Normal file
17
core/migrations/0036_alter_systemsetting_options.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-11 16:22
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0035_remove_heldsale_customer_remove_heldsale_notes_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='systemsetting',
|
||||||
|
options={'permissions': [('view_dashboard', 'Can view dashboard'), ('view_pos', 'Can access POS'), ('view_reports', 'Can view reports'), ('view_accounting', 'Can view accounting'), ('view_hr', 'Can view HR')]},
|
||||||
|
),
|
||||||
|
]
|
||||||
17
core/migrations/0037_alter_systemsetting_options.py
Normal file
17
core/migrations/0037_alter_systemsetting_options.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2026-02-11 16:26
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0036_alter_systemsetting_options'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='systemsetting',
|
||||||
|
options={'permissions': [('view_dashboard', 'Can view dashboard'), ('view_pos', 'Can access POS'), ('view_reports', 'Can view reports'), ('view_accounting', 'Can view accounting'), ('view_hr', 'Can view HR'), ('view_inventory', 'Can view inventory'), ('view_sales', 'Can view sales'), ('view_purchases', 'Can view purchases'), ('view_customers', 'Can view customers'), ('view_suppliers', 'Can view suppliers'), ('view_expenses', 'Can view expenses'), ('view_lpo', 'Can view LPO'), ('view_quotations', 'Can view quotations')]},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -398,6 +398,21 @@ class SystemSetting(models.Model):
|
|||||||
favicon = models.FileField(_("Favicon"), upload_to="business_logos/", blank=True, null=True)
|
favicon = models.FileField(_("Favicon"), upload_to="business_logos/", blank=True, null=True)
|
||||||
vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True)
|
vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True)
|
||||||
registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True)
|
registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True)
|
||||||
|
|
||||||
|
# Loyalty Settings
|
||||||
|
loyalty_enabled = models.BooleanField(_("Enable Loyalty System"), default=False)
|
||||||
|
points_per_currency = models.DecimalField(_("Points Earned per Currency Unit"), max_digits=10, decimal_places=2, default=1.0)
|
||||||
|
currency_per_point = models.DecimalField(_("Currency Value per Point"), max_digits=10, decimal_places=3, default=0.01)
|
||||||
|
min_points_to_redeem = models.PositiveIntegerField(_("Minimum Points to Redeem"), default=100)
|
||||||
|
|
||||||
|
# WhatsApp Gateway (Wablas)
|
||||||
|
wablas_enabled = models.BooleanField(_("Enable WhatsApp Gateway"), default=False)
|
||||||
|
wablas_server_url = models.URLField(_("Wablas Server URL"), blank=True, help_text=_("Example: https://console.wablas.com"))
|
||||||
|
wablas_token = models.CharField(_("Wablas API Token"), max_length=255, blank=True)
|
||||||
|
wablas_secret_key = models.CharField(_("Wablas Secret Key"), max_length=255, blank=True)
|
||||||
|
|
||||||
|
# POS Settings
|
||||||
|
allow_zero_stock_sales = models.BooleanField(_("Allow selling items with 0 stock"), default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = [
|
permissions = [
|
||||||
@ -406,6 +421,14 @@ class SystemSetting(models.Model):
|
|||||||
("view_reports", "Can view reports"),
|
("view_reports", "Can view reports"),
|
||||||
("view_accounting", "Can view accounting"),
|
("view_accounting", "Can view accounting"),
|
||||||
("view_hr", "Can view HR"),
|
("view_hr", "Can view HR"),
|
||||||
|
("view_inventory", "Can view inventory"),
|
||||||
|
("view_sales", "Can view sales"),
|
||||||
|
("view_purchases", "Can view purchases"),
|
||||||
|
("view_customers", "Can view customers"),
|
||||||
|
("view_suppliers", "Can view suppliers"),
|
||||||
|
("view_expenses", "Can view expenses"),
|
||||||
|
("view_lpo", "Can view LPO"),
|
||||||
|
("view_quotations", "Can view quotations"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@ -64,11 +64,14 @@
|
|||||||
<i class="bi bi-chevron-down chevron"></i>
|
<i class="bi bi-chevron-down chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="collapse list-unstyled sub-menu {% if url_name == 'pos' or url_name == 'invoice_create' or url_name == 'invoices' or url_name == 'invoice_detail' or url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' or 'sales/returns' in path %}show{% endif %}" id="salesSubmenu">
|
<ul class="collapse list-unstyled sub-menu {% if url_name == 'pos' or url_name == 'invoice_create' or url_name == 'invoices' or url_name == 'invoice_detail' or url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' or 'sales/returns' in path %}show{% endif %}" id="salesSubmenu">
|
||||||
|
{% if user.is_staff or perms.core.view_pos %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'pos' %}" class="{% if url_name == 'pos' %}active{% endif %}">
|
<a href="{% url 'pos' %}" class="{% if url_name == 'pos' %}active{% endif %}">
|
||||||
<i class="bi bi-shop"></i> {% trans "POS System" %}
|
<i class="bi bi-shop"></i> {% trans "POS System" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.is_staff or perms.core.view_sales %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'invoice_create' %}" class="{% if url_name == 'invoice_create' %}active{% endif %}">
|
<a href="{% url 'invoice_create' %}" class="{% if url_name == 'invoice_create' %}active{% endif %}">
|
||||||
<i class="bi bi-plus-circle"></i> {% trans "New Sales" %}
|
<i class="bi bi-plus-circle"></i> {% trans "New Sales" %}
|
||||||
@ -79,16 +82,21 @@
|
|||||||
<i class="bi bi-file-earmark-text"></i> {% trans "Sales Invoices" %}
|
<i class="bi bi-file-earmark-text"></i> {% trans "Sales Invoices" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.is_staff or perms.core.view_quotations %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'quotations' %}" class="{% if url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' %}active{% endif %}">
|
<a href="{% url 'quotations' %}" class="{% if url_name == 'quotations' or url_name == 'quotation_create' or url_name == 'quotation_detail' %}active{% endif %}">
|
||||||
<i class="bi bi-file-earmark-spreadsheet"></i> {% trans "Quotation" %}
|
<i class="bi bi-file-earmark-spreadsheet"></i> {% trans "Quotation" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.is_staff or perms.core.view_sales %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'sales_returns' %}" class="{% if 'sales/returns' in path %}active{% endif %}">
|
<a href="{% url 'sales_returns' %}" class="{% if 'sales/returns' in path %}active{% endif %}">
|
||||||
<i class="bi bi-arrow-return-left"></i> {% trans "Sales Return" %}
|
<i class="bi bi-arrow-return-left"></i> {% trans "Sales Return" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -99,11 +107,14 @@
|
|||||||
<i class="bi bi-chevron-down chevron"></i>
|
<i class="bi bi-chevron-down chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="collapse list-unstyled sub-menu {% if url_name == 'purchases' or url_name == 'purchase_create' or url_name == 'purchase_detail' or url_name == 'supplier_payments' or 'purchases/returns' in path %}show{% endif %}" id="purchasesSubmenu">
|
<ul class="collapse list-unstyled sub-menu {% if url_name == 'purchases' or url_name == 'purchase_create' or url_name == 'purchase_detail' or url_name == 'supplier_payments' or 'purchases/returns' in path %}show{% endif %}" id="purchasesSubmenu">
|
||||||
|
{% if user.is_staff or perms.core.view_lpo %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'lpo_list' %}" class="{% if url_name == 'lpo_list' or url_name == 'lpo_create' or url_name == 'lpo_detail' %}active{% endif %}">
|
<a href="{% url 'lpo_list' %}" class="{% if url_name == 'lpo_list' or url_name == 'lpo_create' or url_name == 'lpo_detail' %}active{% endif %}">
|
||||||
<i class="bi bi-file-earmark-text"></i> {% trans "Purchase Orders (LPO)" %}
|
<i class="bi bi-file-earmark-text"></i> {% trans "Purchase Orders (LPO)" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.is_staff or perms.core.view_purchases %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'purchase_create' %}" class="{% if url_name == 'purchase_create' %}active{% endif %}">
|
<a href="{% url 'purchase_create' %}" class="{% if url_name == 'purchase_create' %}active{% endif %}">
|
||||||
<i class="bi bi-plus-circle"></i> {% trans "New Purchase" %}
|
<i class="bi bi-plus-circle"></i> {% trans "New Purchase" %}
|
||||||
@ -124,6 +135,7 @@
|
|||||||
<i class="bi bi-arrow-return-right"></i> {% trans "Purchase Return" %}
|
<i class="bi bi-arrow-return-right"></i> {% trans "Purchase Return" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -134,6 +146,7 @@
|
|||||||
<i class="bi bi-chevron-down chevron"></i>
|
<i class="bi bi-chevron-down chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="collapse list-unstyled sub-menu {% if url_name == 'inventory' or url_name == 'barcode_labels' or url_name == 'reports' %}show{% endif %}" id="inventorySubmenu">
|
<ul class="collapse list-unstyled sub-menu {% if url_name == 'inventory' or url_name == 'barcode_labels' or url_name == 'reports' %}show{% endif %}" id="inventorySubmenu">
|
||||||
|
{% if user.is_staff or perms.core.view_inventory %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'inventory' %}" class="{% if url_name == 'inventory' %}active{% endif %}">
|
<a href="{% url 'inventory' %}" class="{% if url_name == 'inventory' %}active{% endif %}">
|
||||||
<i class="bi bi-box-seam"></i> {% trans "Products" %}
|
<i class="bi bi-box-seam"></i> {% trans "Products" %}
|
||||||
@ -144,7 +157,7 @@
|
|||||||
<i class="bi bi-upc-scan"></i> {% trans "Barcode Printing" %}
|
<i class="bi bi-upc-scan"></i> {% trans "Barcode Printing" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -155,6 +168,7 @@
|
|||||||
<i class="bi bi-chevron-down chevron"></i>
|
<i class="bi bi-chevron-down chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="collapse list-unstyled sub-menu {% if url_name == 'expenses' or url_name == 'expense_categories' %}show{% endif %}" id="expensesSubmenu">
|
<ul class="collapse list-unstyled sub-menu {% if url_name == 'expenses' or url_name == 'expense_categories' %}show{% endif %}" id="expensesSubmenu">
|
||||||
|
{% if user.is_staff or perms.core.view_expenses %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'expenses' %}" class="{% if url_name == 'expenses' %}active{% endif %}">
|
<a href="{% url 'expenses' %}" class="{% if url_name == 'expenses' %}active{% endif %}">
|
||||||
<i class="bi bi-receipt"></i> {% trans "Expense List" %}
|
<i class="bi bi-receipt"></i> {% trans "Expense List" %}
|
||||||
@ -165,6 +179,7 @@
|
|||||||
<i class="bi bi-tags"></i> {% trans "Categories" %}
|
<i class="bi bi-tags"></i> {% trans "Categories" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -175,21 +190,24 @@
|
|||||||
<i class="bi bi-chevron-down chevron"></i>
|
<i class="bi bi-chevron-down chevron"></i>
|
||||||
</a>
|
</a>
|
||||||
<ul class="collapse list-unstyled sub-menu {% if url_name == 'customers' or url_name == 'suppliers' %}show{% endif %}" id="contactsSubmenu">
|
<ul class="collapse list-unstyled sub-menu {% if url_name == 'customers' or url_name == 'suppliers' %}show{% endif %}" id="contactsSubmenu">
|
||||||
|
{% if user.is_staff or perms.core.view_customers %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'customers' %}" class="{% if url_name == 'customers' %}active{% endif %}">
|
<a href="{% url 'customers' %}" class="{% if url_name == 'customers' %}active{% endif %}">
|
||||||
<i class="bi bi-people"></i> {% trans "Customers" %}
|
<i class="bi bi-people"></i> {% trans "Customers" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user.is_staff or perms.core.view_suppliers %}
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'suppliers' %}" class="{% if url_name == 'suppliers' %}active{% endif %}">
|
<a href="{% url 'suppliers' %}" class="{% if url_name == 'suppliers' %}active{% endif %}">
|
||||||
<i class="bi bi-truck"></i> {% trans "Suppliers" %}
|
<i class="bi bi-truck"></i> {% trans "Suppliers" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if user.is_superuser or user.is_staff %}
|
{% if user.is_staff or perms.core.view_accounting %}
|
||||||
|
|
||||||
<!-- Accounting Group -->
|
<!-- Accounting Group -->
|
||||||
<li class="sidebar-group-header mt-1">
|
<li class="sidebar-group-header mt-1">
|
||||||
<a href="#accountingSubmenu" data-bs-toggle="collapse" aria-expanded="{% if 'accounting' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
<a href="#accountingSubmenu" data-bs-toggle="collapse" aria-expanded="{% if 'accounting' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
||||||
@ -229,7 +247,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.is_staff or perms.core.view_hr %}
|
||||||
<!-- HR Group -->
|
<!-- HR Group -->
|
||||||
<li class="sidebar-group-header mt-1">
|
<li class="sidebar-group-header mt-1">
|
||||||
<a href="#hrSubmenu" data-bs-toggle="collapse" aria-expanded="{% if 'hr/' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
<a href="#hrSubmenu" data-bs-toggle="collapse" aria-expanded="{% if 'hr/' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
||||||
@ -269,7 +289,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.is_staff or perms.core.view_reports %}
|
||||||
<!-- Reports Group -->
|
<!-- Reports Group -->
|
||||||
<li class="sidebar-group-header mt-1">
|
<li class="sidebar-group-header mt-1">
|
||||||
<a href="#reportsSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'reports' or url_name == 'customer_statement' or url_name == 'supplier_statement' or url_name == 'cashflow_report' or url_name == 'expense_report' %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
<a href="#reportsSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'reports' or url_name == 'customer_statement' or url_name == 'supplier_statement' or url_name == 'cashflow_report' or url_name == 'expense_report' %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
||||||
@ -304,7 +326,9 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.is_staff %}
|
||||||
<!-- System Group -->
|
<!-- System Group -->
|
||||||
<li class="sidebar-group-header mt-1">
|
<li class="sidebar-group-header mt-1">
|
||||||
<a href="#systemSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'settings' or url_name == 'user_management' or url_name == 'cashier_registry' or '/admin/' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
<a href="#systemSubmenu" data-bs-toggle="collapse" aria-expanded="{% if url_name == 'settings' or url_name == 'user_management' or url_name == 'cashier_registry' or '/admin/' in path %}true{% else %}false{% endif %}" class="dropdown-toggle-custom">
|
||||||
@ -378,6 +402,17 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
|
{% if messages %}
|
||||||
|
<div class="me-3">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show mb-0 py-1 px-3 shadow-sm border-0" role="alert" style="font-size: 0.85rem;">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close py-2" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="language-switcher">
|
<div class="language-switcher">
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-flex align-items-center">
|
<form action="{% url 'set_language' %}" method="post" class="d-flex align-items-center">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
@ -478,6 +513,11 @@
|
|||||||
document.addEventListener('submit', function (e) {
|
document.addEventListener('submit', function (e) {
|
||||||
const form = e.target;
|
const form = e.target;
|
||||||
if (form.tagName === 'FORM') {
|
if (form.tagName === 'FORM') {
|
||||||
|
// Don't disable if it's the login form or has a specific class
|
||||||
|
if (form.action.includes('login') || form.classList.contains('no-disable')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (form.getAttribute('data-submitted') === 'true') {
|
if (form.getAttribute('data-submitted') === 'true') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return;
|
return;
|
||||||
@ -486,7 +526,6 @@
|
|||||||
const submitButtons = form.querySelectorAll('button[type="submit"], input[type="submit"]');
|
const submitButtons = form.querySelectorAll('button[type="submit"], input[type="submit"]');
|
||||||
submitButtons.forEach(btn => {
|
submitButtons.forEach(btn => {
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
// Add spinner if it is a button and not already there
|
|
||||||
if (btn.tagName === 'BUTTON' && !btn.querySelector('.spinner-border')) {
|
if (btn.tagName === 'BUTTON' && !btn.querySelector('.spinner-border')) {
|
||||||
const originalText = btn.innerHTML;
|
const originalText = btn.innerHTML;
|
||||||
btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${originalText}`;
|
btn.innerHTML = `<span class="spinner-border spinner-border-sm me-2"></span>${originalText}`;
|
||||||
@ -494,6 +533,32 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cleanup stale modal backdrops and body classes aggressively
|
||||||
|
(function() {
|
||||||
|
function cleanupModals() {
|
||||||
|
const backdrops = document.querySelectorAll('.modal-backdrop');
|
||||||
|
if (backdrops.length > 0) {
|
||||||
|
console.log('Cleaning up ' + backdrops.length + ' stale backdrops');
|
||||||
|
backdrops.forEach(el => el.remove());
|
||||||
|
}
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
document.body.style.paddingRight = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run multiple times to ensure cleanup
|
||||||
|
cleanupModals();
|
||||||
|
window.addEventListener('DOMContentLoaded', cleanupModals);
|
||||||
|
window.addEventListener('load', cleanupModals);
|
||||||
|
|
||||||
|
// Periodically check for 2 seconds
|
||||||
|
let attempts = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
cleanupModals();
|
||||||
|
if (++attempts > 10) clearInterval(interval);
|
||||||
|
}, 500);
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -391,6 +391,11 @@ def group_details_api(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def pos(request):
|
def pos(request):
|
||||||
|
# Permission check
|
||||||
|
if not (request.user.is_staff or request.user.has_perm('core.view_pos')):
|
||||||
|
messages.error(request, _("You do not have permission to access the POS system."))
|
||||||
|
return redirect('index')
|
||||||
|
|
||||||
# Check for active session
|
# Check for active session
|
||||||
active_session = CashierSession.objects.filter(user=request.user, status='active').first()
|
active_session = CashierSession.objects.filter(user=request.user, status='active').first()
|
||||||
if not active_session:
|
if not active_session:
|
||||||
@ -2042,18 +2047,91 @@ def get_customer_loyalty_api(request, pk):
|
|||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def hold_sale_api(request):
|
def hold_sale_api(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
customer_name = ""
|
||||||
|
customer_id = data.get('customer_id')
|
||||||
|
if customer_id:
|
||||||
|
try:
|
||||||
|
customer = Customer.objects.get(id=customer_id)
|
||||||
|
customer_name = customer.name
|
||||||
|
except (Customer.DoesNotExist, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Store everything in cart_data JSON
|
||||||
|
cart_info = {
|
||||||
|
'items': data.get('items', []),
|
||||||
|
'customer_id': customer_id,
|
||||||
|
'customer_name': customer_name
|
||||||
|
}
|
||||||
|
|
||||||
|
HeldSale.objects.create(
|
||||||
|
created_by=request.user,
|
||||||
|
cart_data=json.dumps(cart_info),
|
||||||
|
customer_name=customer_name,
|
||||||
|
note=data.get('notes', "")
|
||||||
|
)
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error holding sale: {str(e)}")
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
return JsonResponse({'success': False, 'error': 'Only POST allowed'})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def get_held_sales_api(request):
|
def get_held_sales_api(request):
|
||||||
return JsonResponse({'sales': []})
|
sales = HeldSale.objects.filter(created_by=request.user).order_by('-created_at')
|
||||||
|
sales_list = []
|
||||||
|
for s in sales:
|
||||||
|
try:
|
||||||
|
cart_info = json.loads(s.cart_data)
|
||||||
|
item_count = len(cart_info.get('items', []))
|
||||||
|
except:
|
||||||
|
item_count = 0
|
||||||
|
|
||||||
|
sales_list.append({
|
||||||
|
'id': s.id,
|
||||||
|
'customer_name': s.customer_name or _("Walk-in Customer"),
|
||||||
|
'note': s.note,
|
||||||
|
'created_at': s.created_at.strftime('%Y-%m-%d %H:%M'),
|
||||||
|
'item_count': item_count
|
||||||
|
})
|
||||||
|
return JsonResponse({'success': True, 'held_sales': sales_list})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def recall_held_sale_api(request, pk):
|
def recall_held_sale_api(request, pk):
|
||||||
return JsonResponse({'success': True})
|
sale = get_object_or_404(HeldSale, pk=pk, created_by=request.user)
|
||||||
|
try:
|
||||||
|
cart_info = json.loads(sale.cart_data)
|
||||||
|
|
||||||
|
# Support both old format (just items) and new format (dict with items, customer_id, etc)
|
||||||
|
if isinstance(cart_info, list):
|
||||||
|
items = cart_info
|
||||||
|
customer_id = ""
|
||||||
|
customer_name = sale.customer_name
|
||||||
|
else:
|
||||||
|
items = cart_info.get('items', [])
|
||||||
|
customer_id = cart_info.get('customer_id', "")
|
||||||
|
customer_name = cart_info.get('customer_name', sale.customer_name)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'success': True,
|
||||||
|
'items': items,
|
||||||
|
'customer_id': customer_id,
|
||||||
|
'customer_name': customer_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Delete the held sale once recalled
|
||||||
|
sale.delete()
|
||||||
|
return JsonResponse(data)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error recalling sale: {str(e)}")
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def delete_held_sale_api(request, pk):
|
def delete_held_sale_api(request, pk):
|
||||||
|
sale = get_object_or_404(HeldSale, pk=pk, created_by=request.user)
|
||||||
|
sale.delete()
|
||||||
return JsonResponse({'success': True})
|
return JsonResponse({'success': True})
|
||||||
@login_required
|
@login_required
|
||||||
def backup_database(request):
|
def backup_database(request):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user