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 %}
-
+
{% 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 %}
+
+
+
+{% 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" %}
+
+
-
-
+
+
| {% trans "Date" %} |
{% trans "Employee" %} |
{% trans "Check In" %} |
{% trans "Check Out" %} |
- {% trans "Notes" %} |
+ {% trans "Actions" %} |
- {% for attendance in attendances %}
+ {% for att in attendances %}
- | {{ 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:"--" }} |
+
+
+
+
+ |
{% empty %}
- | {% trans "No records found." %} |
+ {% trans "No attendance records found." %} |
{% endfor %}
+
+ {% 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 %}
+
+
+
+{% 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 "Name (English)" %} |
{% trans "Name (Arabic)" %} |
{% trans "Employees" %} |
+ {% trans "Actions" %} |
@@ -22,10 +28,15 @@
{{ department.name_en }} |
{{ department.name_ar }} |
{{ department.employees.count }} |
+
+
+
+
+ |
{% empty %}
- | {% trans "No departments found." %} |
+ {% trans "No departments found." %} |
{% 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 %}
+
+ {% 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])