diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 268182e..98f88ae 100644 Binary files a/core/__pycache__/context_processors.cpython-311.pyc and b/core/__pycache__/context_processors.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index a26b97d..f34b198 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index f373a8f..09e0de8 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index b8fac4d..82f9e48 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/context_processors.py b/core/context_processors.py index 33b1942..5ce8c85 100644 --- a/core/context_processors.py +++ b/core/context_processors.py @@ -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 {} \ No newline at end of file + return {'decimal_places': 3} \ No newline at end of file diff --git a/core/migrations/0012_systemsetting_decimal_places.py b/core/migrations/0012_systemsetting_decimal_places.py new file mode 100644 index 0000000..8a2c11a --- /dev/null +++ b/core/migrations/0012_systemsetting_decimal_places.py @@ -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'), + ), + ] diff --git a/core/migrations/__pycache__/0012_systemsetting_decimal_places.cpython-311.pyc b/core/migrations/__pycache__/0012_systemsetting_decimal_places.cpython-311.pyc new file mode 100644 index 0000000..4e228af Binary files /dev/null and b/core/migrations/__pycache__/0012_systemsetting_decimal_places.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 697ec63..09650ba 100644 --- a/core/models.py +++ b/core/models.py @@ -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 \ No newline at end of file + return self.business_name diff --git a/core/templates/core/customers.html b/core/templates/core/customers.html index 85d777d..f32865c 100644 --- a/core/templates/core/customers.html +++ b/core/templates/core/customers.html @@ -34,7 +34,7 @@
- +
@@ -65,89 +65,6 @@ - - - - - - - {% empty %} - - - {% endfor %}
{% trans "Name" %}
- -

{% trans "No customers found." %}

-
@@ -164,32 +81,68 @@
-
- {% csrf_token %} -
+{% endblock %} + +{% block scripts %} + {% endblock %} \ No newline at end of file diff --git a/core/templates/core/inventory.html b/core/templates/core/inventory.html index 126e0af..8f20e80 100644 --- a/core/templates/core/inventory.html +++ b/core/templates/core/inventory.html @@ -92,7 +92,7 @@
- +
@@ -153,184 +153,6 @@ - - - - - - {% empty %}
{% trans "Item" %}
@@ -349,7 +171,7 @@
- +
@@ -375,37 +197,6 @@ - - - {% empty %} @@ -421,7 +212,7 @@
-
{% trans "Category Name" %}
{% trans "No categories found." %}
+
@@ -447,40 +238,6 @@ - - - {% empty %} @@ -494,6 +251,70 @@ + + + + + + + +
{% trans "Unit Name" %}
{% trans "No units found." %}
+
@@ -159,7 +164,7 @@ {% for pm in payment_methods %} - + -
{% trans "Name (EN)" %}
{{ pm.name_en }} {{ pm.name_ar }} @@ -236,7 +241,7 @@ {% empty %}
+
{% trans "No payment methods found." %} @@ -256,39 +261,37 @@
+ + + + + + + + + {% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 2770656..f0f72a4 100644 --- a/core/urls.py +++ b/core/urls.py @@ -55,6 +55,7 @@ urlpatterns = [ path('customers/add/', views.add_customer, name='add_customer'), path('customers/edit//', views.edit_customer, name='edit_customer'), path('customers/delete//', 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//', views.edit_payment_method, name='edit_payment_method'), path('settings/payment-methods/delete//', views.delete_payment_method, name='delete_payment_method'), -] + path('api/add-payment-method-ajax/', views.add_payment_method_ajax, name='add_payment_method_ajax'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index bf82a9d..ca1e637 100644 --- a/core/views.py +++ b/core/views.py @@ -1,3 +1,4 @@ +from django.contrib.auth.models import User, Group, Permission from django.urls import reverse import random import string @@ -74,10 +75,12 @@ def inventory(request): products = Product.objects.all().select_related('category', 'unit', 'supplier') categories = Category.objects.all() suppliers = Supplier.objects.all() + units = Unit.objects.all() context = { 'products': products, 'categories': categories, - 'suppliers': suppliers + 'suppliers': suppliers, + 'units': units } return render(request, 'core/inventory.html', context) @@ -640,7 +643,7 @@ def purchase_return_create(request): purchases = Purchase.objects.all().order_by('-created_at') return render(request, 'core/purchase_return_create.html', { 'products': products, - 'suppliers': suppliers, + 'customers': suppliers, 'purchases': purchases }) @@ -748,6 +751,7 @@ def settings_view(request): settings.email = request.POST.get('email') settings.currency_symbol = request.POST.get('currency_symbol') settings.tax_rate = request.POST.get('tax_rate') + settings.decimal_places = request.POST.get('decimal_places', 3) settings.vat_number = request.POST.get('vat_number') settings.registration_number = request.POST.get('registration_number') @@ -756,7 +760,7 @@ def settings_view(request): settings.save() messages.success(request, "Settings updated successfully!") - return redirect('settings') + return redirect(reverse('settings') + '#profile') payment_methods = PaymentMethod.objects.all() @@ -773,7 +777,7 @@ def add_payment_method(request): is_active = request.POST.get('is_active') == 'on' PaymentMethod.objects.create(name_en=name_en, name_ar=name_ar, is_active=is_active) messages.success(request, "Payment method added successfully!") - return redirect('settings') + return redirect(reverse('settings') + '#payments') @login_required def edit_payment_method(request, pk): @@ -784,14 +788,14 @@ def edit_payment_method(request, pk): pm.is_active = request.POST.get('is_active') == 'on' pm.save() messages.success(request, "Payment method updated successfully!") - return redirect('settings') + return redirect(reverse('settings') + '#payments') @login_required def delete_payment_method(request, pk): pm = get_object_or_404(PaymentMethod, pk=pk) pm.delete() messages.success(request, "Payment method deleted successfully!") - return redirect('settings') + return redirect(reverse('settings') + '#payments') @login_required def add_customer(request): @@ -1165,9 +1169,10 @@ def user_management(request): messages.error(request, "Access denied.") return redirect('index') - from django.contrib.auth.models import User, Group users = User.objects.all().prefetch_related('groups') - groups = Group.objects.all() + groups = Group.objects.all().prefetch_related('permissions') + # Filter for relevant permissions (core and auth) + permissions = Permission.objects.select_related('content_type').all().order_by('content_type__app_label', 'codename') if request.method == 'POST': action = request.POST.get('action') @@ -1175,19 +1180,62 @@ def user_management(request): username = request.POST.get('username') password = request.POST.get('password') email = request.POST.get('email') - group_id = request.POST.get('group') + group_ids = request.POST.getlist('groups') if User.objects.filter(username=username).exists(): messages.error(request, "Username already exists.") else: user = User.objects.create_user(username=username, email=email, password=password) - if group_id: - group = Group.objects.get(id=group_id) - user.groups.add(group) + if group_ids: + selected_groups = Group.objects.filter(id__in=group_ids) + user.groups.set(selected_groups) user.is_staff = True user.save() messages.success(request, f"User {username} created successfully.") + elif action == 'edit_user': + user_id = request.POST.get('user_id') + user = get_object_or_404(User, id=user_id) + user.email = request.POST.get('email') + group_ids = request.POST.getlist('groups') + selected_groups = Group.objects.filter(id__in=group_ids) + user.groups.set(selected_groups) + + password = request.POST.get('password') + if password: + user.set_password(password) + + user.save() + messages.success(request, f"User {user.username} updated.") + + elif action == 'add_group': + name = request.POST.get('name') + permission_ids = request.POST.getlist('permissions') + if Group.objects.filter(name=name).exists(): + messages.error(request, "Group name already exists.") + else: + group = Group.objects.create(name=name) + if permission_ids: + perms = Permission.objects.filter(id__in=permission_ids) + group.permissions.set(perms) + messages.success(request, f"Group {name} created successfully.") + + elif action == 'edit_group': + group_id = request.POST.get('group_id') + group = get_object_or_404(Group, id=group_id) + group.name = request.POST.get('name') + permission_ids = request.POST.getlist('permissions') + perms = Permission.objects.filter(id__in=permission_ids) + group.permissions.set(perms) + group.save() + messages.success(request, f"Group {group.name} updated.") + + elif action == 'delete_group': + group_id = request.POST.get('group_id') + group = get_object_or_404(Group, id=group_id) + group.delete() + messages.success(request, "Group deleted.") + elif action == 'toggle_status': user_id = request.POST.get('user_id') user = get_object_or_404(User, id=user_id) @@ -1198,6 +1246,71 @@ def user_management(request): user.save() messages.success(request, f"User {user.username} status updated.") - return redirect('user_management') + # Determine redirect hash based on action + target_hash = "" + if action in ['add_group', 'edit_group', 'delete_group']: + target_hash = "#groups" - return render(request, 'core/users.html', {'users': users, 'groups': groups}) \ No newline at end of file + 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)