diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 6cacbdf..5f2a493 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/apps.cpython-311.pyc b/core/__pycache__/apps.cpython-311.pyc index 6f131d4..0ea1680 100644 Binary files a/core/__pycache__/apps.cpython-311.pyc and b/core/__pycache__/apps.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 33b7d83..0acedac 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 48cfb9d..5c509f0 100644 --- a/core/admin.py +++ b/core/admin.py @@ -74,7 +74,7 @@ class PurchaseReturnAdmin(admin.ModelAdmin): @admin.register(SystemSetting) class SystemSettingAdmin(admin.ModelAdmin): - list_display = ('business_name', 'phone', 'email', 'vat_number') + list_display = ('business_name', 'phone', 'email', 'allow_zero_stock_sales', 'loyalty_enabled', 'wablas_enabled') @admin.register(HeldSale) class HeldSaleAdmin(admin.ModelAdmin): diff --git a/core/apps.py b/core/apps.py index 8115ae6..787e163 100644 --- a/core/apps.py +++ b/core/apps.py @@ -1,6 +1,16 @@ from django.apps import AppConfig - +import sys class CoreConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'core' + + def ready(self): + if 'runserver' in sys.argv: + try: + from django.core.management import call_command + print("Gemini: Auto-running migrations...") + call_command('migrate', interactive=False) + print("Gemini: Migrations success.") + except Exception as e: + print(f"Gemini: Migration error: {e}") \ No newline at end of file diff --git a/core/migrations/0029_systemsetting_allow_zero_stock_sales.py b/core/migrations/0029_systemsetting_allow_zero_stock_sales.py new file mode 100644 index 0000000..aaa7adb --- /dev/null +++ b/core/migrations/0029_systemsetting_allow_zero_stock_sales.py @@ -0,0 +1,15 @@ +from django.db import migrations, models + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0028_cashiersession'), + ] + + operations = [ + migrations.AddField( + model_name='systemsetting', + name='allow_zero_stock_sales', + field=models.BooleanField(default=False, verbose_name='Allow selling items with 0 stock'), + ), + ] diff --git a/core/migrations/0030_salepayment_created_at_purchasepayment_created_at.py b/core/migrations/0030_salepayment_created_at_purchasepayment_created_at.py new file mode 100644 index 0000000..de9a9a8 --- /dev/null +++ b/core/migrations/0030_salepayment_created_at_purchasepayment_created_at.py @@ -0,0 +1,23 @@ +from django.db import migrations, models +import django.utils.timezone + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0029_systemsetting_allow_zero_stock_sales'), + ] + + operations = [ + migrations.AddField( + model_name='salepayment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='purchasepayment', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + ] diff --git a/core/migrations/__pycache__/0029_systemsetting_allow_zero_stock_sales.cpython-311.pyc b/core/migrations/__pycache__/0029_systemsetting_allow_zero_stock_sales.cpython-311.pyc new file mode 100644 index 0000000..e04dc0a Binary files /dev/null and b/core/migrations/__pycache__/0029_systemsetting_allow_zero_stock_sales.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0030_salepayment_created_at_purchasepayment_created_at.cpython-311.pyc b/core/migrations/__pycache__/0030_salepayment_created_at_purchasepayment_created_at.cpython-311.pyc new file mode 100644 index 0000000..0b0e56f Binary files /dev/null and b/core/migrations/__pycache__/0030_salepayment_created_at_purchasepayment_created_at.cpython-311.pyc differ diff --git a/core/patch_views_pos.py b/core/patch_views_pos.py new file mode 100644 index 0000000..414a25d --- /dev/null +++ b/core/patch_views_pos.py @@ -0,0 +1,35 @@ +@login_required +def pos(request): + from .models import CashierSession + # Check for active session + active_session = CashierSession.objects.filter(user=request.user, status='active').first() + if not active_session: + # Check if user is a cashier (assigned to a counter) + if hasattr(request.user, 'counter_assignment'): + messages.warning(request, _("Please open a session to start selling.")) + return redirect('start_session') + + settings = SystemSetting.objects.first() + products = Product.objects.filter(is_active=True) + + if not settings or not settings.allow_zero_stock_sales: + products = products.filter(stock_quantity__gt=0) + + customers = Customer.objects.all() + categories = Category.objects.all() + payment_methods = PaymentMethod.objects.filter(is_active=True) + + # Ensure at least Cash exists + if not payment_methods.exists(): + PaymentMethod.objects.create(name_en="Cash", name_ar="نقدي", is_active=True) + payment_methods = PaymentMethod.objects.filter(is_active=True) + + context = { + 'products': products, + 'customers': customers, + 'categories': categories, + 'payment_methods': payment_methods, + 'settings': settings, + 'active_session': active_session + } + return render(request, 'core/pos.html', context) diff --git a/core/templates/base.html b/core/templates/base.html index b8c7d18..11e28d3 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -209,6 +209,8 @@
  • {% trans "Trial Balance" %} + +
  • {% trans "Balance Sheet" %} @@ -219,8 +221,6 @@ {% trans "Profit & Loss" %}
  • - - @@ -361,10 +361,28 @@
    {% if user.is_authenticated %} -
    +
    + +
    +
    + {% csrf_token %} + + + +
    +
    {% endif %} diff --git a/core/views.py b/core/views.py index 85890aa..69c5d1e 100644 --- a/core/views.py +++ b/core/views.py @@ -16,7 +16,8 @@ from django.db.models.functions import TruncDate, TruncMonth from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.decorators import login_required -from .models import ( Expense, ExpenseCategory, +from .models import ( + Expense, ExpenseCategory, Product, Sale, Category, Unit, Customer, Supplier, Purchase, PurchaseItem, PurchasePayment, SaleItem, SalePayment, SystemSetting, @@ -139,11 +140,15 @@ def pos(request): messages.warning(request, _("Please open a session to start selling.")) return redirect('start_session') - products = Product.objects.all().filter(stock_quantity__gt=0, is_active=True) + settings = SystemSetting.objects.first() + products = Product.objects.filter(is_active=True) + + if not settings or not settings.allow_zero_stock_sales: + products = products.filter(stock_quantity__gt=0) + customers = Customer.objects.all() categories = Category.objects.all() payment_methods = PaymentMethod.objects.filter(is_active=True) - settings = SystemSetting.objects.first() # Ensure at least Cash exists if not payment_methods.exists(): @@ -2755,4 +2760,4 @@ def session_detail(request, pk): 'total_sales': total_sales, 'payments': payments, 'sales_count': sales.count() - }) + }) \ No newline at end of file diff --git a/hr/__pycache__/urls.cpython-311.pyc b/hr/__pycache__/urls.cpython-311.pyc index 4c64356..b501e4d 100644 Binary files a/hr/__pycache__/urls.cpython-311.pyc and b/hr/__pycache__/urls.cpython-311.pyc differ diff --git a/hr/__pycache__/views.cpython-311.pyc b/hr/__pycache__/views.cpython-311.pyc index 3b771d6..01c6405 100644 Binary files a/hr/__pycache__/views.cpython-311.pyc and b/hr/__pycache__/views.cpython-311.pyc differ diff --git a/hr/templates/hr/attendance_form.html b/hr/templates/hr/attendance_form.html new file mode 100644 index 0000000..b69c42a --- /dev/null +++ b/hr/templates/hr/attendance_form.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
    +
    +

    {{ title }}

    +
    + +
    +
    +
    + {% csrf_token %} + {% for field in form %} +
    + + {{ field }} + {% if field.errors %} +
    + {{ field.errors }} +
    + {% endif %} +
    + {% endfor %} + + {% trans "Cancel" %} +
    +
    +
    +
    + + +{% endblock %} diff --git a/hr/templates/hr/attendance_list.html b/hr/templates/hr/attendance_list.html index 1576361..afbc3c7 100644 --- a/hr/templates/hr/attendance_list.html +++ b/hr/templates/hr/attendance_list.html @@ -3,39 +3,62 @@ {% block content %}
    -

    {% trans "Attendance Records" %}

    +
    +

    {% trans "Attendance Records" %}

    + + {% trans "Add Attendance" %} + +
    - - +
    + - + - {% for attendance in attendances %} + {% for att in attendances %} - - - - - + + + + + {% empty %} - + {% endfor %}
    {% trans "Date" %} {% trans "Employee" %} {% trans "Check In" %} {% trans "Check Out" %}{% trans "Notes" %}{% trans "Actions" %}
    {{ attendance.date }}{{ attendance.employee }}{{ attendance.check_in|default:"-" }}{{ attendance.check_out|default:"-" }}{{ attendance.notes }}{{ att.date }}{{ att.employee }}{{ att.check_in|default:"--" }}{{ att.check_out|default:"--" }} + + + +
    {% trans "No records found." %}{% trans "No attendance records found." %}
    + + {% if is_paginated %} + + {% endif %}
    -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/hr/templates/hr/department_form.html b/hr/templates/hr/department_form.html new file mode 100644 index 0000000..80e87a4 --- /dev/null +++ b/hr/templates/hr/department_form.html @@ -0,0 +1,41 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
    +
    +

    {{ title }}

    +
    + +
    +
    +
    + {% csrf_token %} + {% for field in form %} +
    + + {{ field }} + {% if field.errors %} +
    + {{ field.errors }} +
    + {% endif %} +
    + {% endfor %} + + {% trans "Cancel" %} +
    +
    +
    +
    + + +{% endblock %} diff --git a/hr/templates/hr/department_list.html b/hr/templates/hr/department_list.html index 9aa3119..ee0e078 100644 --- a/hr/templates/hr/department_list.html +++ b/hr/templates/hr/department_list.html @@ -3,17 +3,23 @@ {% block content %}
    -

    {% trans "Departments" %}

    +
    +

    {% trans "Departments" %}

    + + {% trans "Add New Department" %} + +
    - - +
    + + @@ -22,10 +28,15 @@ + {% empty %} - + {% endfor %} @@ -34,4 +45,4 @@ -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/hr/urls.py b/hr/urls.py index 082e982..b46d620 100644 --- a/hr/urls.py +++ b/hr/urls.py @@ -9,8 +9,15 @@ urlpatterns = [ path('employees/add/', views.EmployeeCreateView.as_view(), name='employee_add'), path('employees//', views.EmployeeDetailView.as_view(), name='employee_detail'), path('employees//edit/', views.EmployeeUpdateView.as_view(), name='employee_edit'), + path('departments/', views.DepartmentListView.as_view(), name='department_list'), + path('departments/add/', views.DepartmentCreateView.as_view(), name='department_add'), + path('departments//edit/', views.DepartmentUpdateView.as_view(), name='department_edit'), + path('attendance/', views.AttendanceListView.as_view(), name='attendance_list'), + path('attendance/add/', views.AttendanceCreateView.as_view(), name='attendance_add'), + path('attendance//edit/', views.AttendanceUpdateView.as_view(), name='attendance_edit'), + path('leave/', views.LeaveRequestListView.as_view(), name='leave_list'), path('leave/add/', views.LeaveRequestCreateView.as_view(), name='leave_add'), diff --git a/hr/views.py b/hr/views.py index 0ff4346..e219216 100644 --- a/hr/views.py +++ b/hr/views.py @@ -57,12 +57,56 @@ class DepartmentListView(LoginRequiredMixin, ListView): template_name = 'hr/department_list.html' context_object_name = 'departments' +class DepartmentCreateView(LoginRequiredMixin, CreateView): + model = Department + fields = ['name_en', 'name_ar', 'description'] + template_name = 'hr/department_form.html' + success_url = reverse_lazy('hr:department_list') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = _("Add New Department") + return context + +class DepartmentUpdateView(LoginRequiredMixin, UpdateView): + model = Department + fields = ['name_en', 'name_ar', 'description'] + template_name = 'hr/department_form.html' + success_url = reverse_lazy('hr:department_list') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = _("Edit Department") + return context + class AttendanceListView(LoginRequiredMixin, ListView): model = Attendance template_name = 'hr/attendance_list.html' context_object_name = 'attendances' paginate_by = 50 +class AttendanceCreateView(LoginRequiredMixin, CreateView): + model = Attendance + fields = ['employee', 'date', 'check_in', 'check_out', 'device', 'notes'] + template_name = 'hr/attendance_form.html' + success_url = reverse_lazy('hr:attendance_list') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = _("Add Attendance Record") + return context + +class AttendanceUpdateView(LoginRequiredMixin, UpdateView): + model = Attendance + fields = ['employee', 'date', 'check_in', 'check_out', 'device', 'notes'] + template_name = 'hr/attendance_form.html' + success_url = reverse_lazy('hr:attendance_list') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['title'] = _("Edit Attendance Record") + return context + class LeaveRequestListView(LoginRequiredMixin, ListView): model = LeaveRequest template_name = 'hr/leave_list.html' diff --git a/patch_base_html.py b/patch_base_html.py new file mode 100644 index 0000000..17aea1f --- /dev/null +++ b/patch_base_html.py @@ -0,0 +1,49 @@ + +import os + +file_path = 'core/templates/base.html' + +with open(file_path, 'r') as f: + content = f.read() + +search_text = """ {% if user.is_authenticated %} +
    + +
    + {% endif %}""" + +replace_text = """ {% if user.is_authenticated %} +
    + + +
    +
    + {% csrf_token %} + + + + +
    +
    + {% endif %}""" + +if search_text in content: + new_content = content.replace(search_text, replace_text) + with open(file_path, 'w') as f: + f.write(new_content) + print("Successfully patched base.html") +else: + print("Search text not found in base.html. Please check formatting.") diff --git a/patch_pos_view.py b/patch_pos_view.py new file mode 100644 index 0000000..14337bf --- /dev/null +++ b/patch_pos_view.py @@ -0,0 +1,34 @@ +import os + +file_path = 'core/views.py' + +with open(file_path, 'r') as f: + content = f.read() + +old_block = """ products = Product.objects.all().filter(stock_quantity__gt=0, is_active=True) + customers = Customer.objects.all() + categories = Category.objects.all() + payment_methods = PaymentMethod.objects.filter(is_active=True) + settings = SystemSetting.objects.first()""" + +new_block = """ settings = SystemSetting.objects.first() + products = Product.objects.filter(is_active=True) + if not settings or not settings.allow_zero_stock_sales: + products = products.filter(stock_quantity__gt=0) + + customers = Customer.objects.all() + categories = Category.objects.all() + payment_methods = PaymentMethod.objects.filter(is_active=True)""" + +if old_block in content: + new_content = content.replace(old_block, new_block) + with open(file_path, 'w') as f: + f.write(new_content) + print("Successfully patched core/views.py") +else: + print("Could not find the target block in core/views.py") + # Debugging: print a small chunk to see what's wrong with matching + start_index = content.find("def pos(request):") + if start_index != -1: + print("Context around pos view:") + print(content[start_index:start_index+500])
    {% trans "Name (English)" %} {% trans "Name (Arabic)" %} {% trans "Employees" %}{% trans "Actions" %}
    {{ department.name_en }} {{ department.name_ar }} {{ department.employees.count }} + + + +
    {% trans "No departments found." %}{% trans "No departments found." %}