adding zero stock
This commit is contained in:
parent
a59b0c9341
commit
6b4a3fe6e7
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -74,7 +74,7 @@ class PurchaseReturnAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(SystemSetting)
|
@admin.register(SystemSetting)
|
||||||
class SystemSettingAdmin(admin.ModelAdmin):
|
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)
|
@admin.register(HeldSale)
|
||||||
class HeldSaleAdmin(admin.ModelAdmin):
|
class HeldSaleAdmin(admin.ModelAdmin):
|
||||||
|
|||||||
12
core/apps.py
12
core/apps.py
@ -1,6 +1,16 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
import sys
|
||||||
|
|
||||||
class CoreConfig(AppConfig):
|
class CoreConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'core'
|
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}")
|
||||||
15
core/migrations/0029_systemsetting_allow_zero_stock_sales.py
Normal file
15
core/migrations/0029_systemsetting_allow_zero_stock_sales.py
Normal file
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -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,
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
35
core/patch_views_pos.py
Normal file
35
core/patch_views_pos.py
Normal file
@ -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)
|
||||||
@ -209,6 +209,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="{% url 'trial_balance' %}" class="{% if url_name == 'trial_balance' %}active{% endif %}">
|
<a href="{% url 'trial_balance' %}" class="{% if url_name == 'trial_balance' %}active{% endif %}">
|
||||||
<i class="bi bi-check2-square"></i> {% trans "Trial Balance" %}
|
<i class="bi bi-check2-square"></i> {% trans "Trial Balance" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'balance_sheet' %}" class="{% if url_name == 'balance_sheet' %}active{% endif %}">
|
<a href="{% url 'balance_sheet' %}" class="{% if url_name == 'balance_sheet' %}active{% endif %}">
|
||||||
<i class="bi bi-journal-check"></i> {% trans "Balance Sheet" %}
|
<i class="bi bi-journal-check"></i> {% trans "Balance Sheet" %}
|
||||||
@ -219,8 +221,6 @@
|
|||||||
<i class="bi bi-bar-chart"></i> {% trans "Profit & Loss" %}
|
<i class="bi bi-bar-chart"></i> {% trans "Profit & Loss" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
@ -361,10 +361,28 @@
|
|||||||
<!-- Page Content -->
|
<!-- Page Content -->
|
||||||
<div id="content" {% if not user.is_authenticated %}style="margin-left: 0; width: 100%;"{% endif %}>
|
<div id="content" {% if not user.is_authenticated %}style="margin-left: 0; width: 100%;"{% endif %}>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<div class="p-3">
|
<div class="p-3 d-flex justify-content-between align-items-center">
|
||||||
<button type="button" id="sidebarCollapse" class="btn btn-light shadow-sm">
|
<button type="button" id="sidebarCollapse" class="btn btn-light shadow-sm">
|
||||||
<i class="bi bi-list fs-5"></i>
|
<i class="bi bi-list fs-5"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div class="language-switcher">
|
||||||
|
<form action="{% url 'set_language' %}" method="post" class="d-flex align-items-center">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="next" value="{{ request.get_full_path|default:'/' }}">
|
||||||
|
<i class="bi bi-globe2 me-2 text-muted"></i>
|
||||||
|
<select name="language" class="form-select form-select-sm shadow-sm border-0" style="width: auto; background-color: #f8f9fa;" onchange="this.form.submit()">
|
||||||
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
{% get_available_languages as LANGUAGES %}
|
||||||
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
|
{% for language in languages %}
|
||||||
|
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
|
||||||
|
{{ language.name_local }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,8 @@ from django.db.models.functions import TruncDate, TruncMonth
|
|||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from .models import ( Expense, ExpenseCategory,
|
from .models import (
|
||||||
|
Expense, ExpenseCategory,
|
||||||
Product, Sale, Category, Unit, Customer, Supplier,
|
Product, Sale, Category, Unit, Customer, Supplier,
|
||||||
Purchase, PurchaseItem, PurchasePayment,
|
Purchase, PurchaseItem, PurchasePayment,
|
||||||
SaleItem, SalePayment, SystemSetting,
|
SaleItem, SalePayment, SystemSetting,
|
||||||
@ -139,11 +140,15 @@ def pos(request):
|
|||||||
messages.warning(request, _("Please open a session to start selling."))
|
messages.warning(request, _("Please open a session to start selling."))
|
||||||
return redirect('start_session')
|
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()
|
customers = Customer.objects.all()
|
||||||
categories = Category.objects.all()
|
categories = Category.objects.all()
|
||||||
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
settings = SystemSetting.objects.first()
|
|
||||||
|
|
||||||
# Ensure at least Cash exists
|
# Ensure at least Cash exists
|
||||||
if not payment_methods.exists():
|
if not payment_methods.exists():
|
||||||
@ -2755,4 +2760,4 @@ def session_detail(request, pk):
|
|||||||
'total_sales': total_sales,
|
'total_sales': total_sales,
|
||||||
'payments': payments,
|
'payments': payments,
|
||||||
'sales_count': sales.count()
|
'sales_count': sales.count()
|
||||||
})
|
})
|
||||||
Binary file not shown.
Binary file not shown.
41
hr/templates/hr/attendance_form.html
Normal file
41
hr/templates/hr/attendance_form.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||||
|
<h1 class="h3 mb-0 text-gray-800">{{ title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{{ field.label }}</label>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="text-danger small">
|
||||||
|
{{ field.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
|
||||||
|
<a href="{% url 'hr:attendance_list' %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Add bootstrap classes to form fields if not already there
|
||||||
|
document.querySelectorAll('input, select, textarea').forEach(el => {
|
||||||
|
if (!el.classList.contains('form-control') && !el.classList.contains('form-select')) {
|
||||||
|
if (el.tagName === 'SELECT') el.classList.add('form-select');
|
||||||
|
else el.classList.add('form-control');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -3,39 +3,62 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<h1 class="h3 mb-4 text-gray-800">{% trans "Attendance Records" %}</h1>
|
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||||
|
<h1 class="h3 mb-0 text-gray-800">{% trans "Attendance Records" %}</h1>
|
||||||
|
<a href="{% url 'hr:attendance_add' %}" class="btn btn-primary shadow-sm">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> {% trans "Add Attendance" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered" width="100%" cellspacing="0">
|
<table class="table table-bordered table-hover" width="100%" cellspacing="0">
|
||||||
<thead>
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Date" %}</th>
|
<th>{% trans "Date" %}</th>
|
||||||
<th>{% trans "Employee" %}</th>
|
<th>{% trans "Employee" %}</th>
|
||||||
<th>{% trans "Check In" %}</th>
|
<th>{% trans "Check In" %}</th>
|
||||||
<th>{% trans "Check Out" %}</th>
|
<th>{% trans "Check Out" %}</th>
|
||||||
<th>{% trans "Notes" %}</th>
|
<th class="text-center">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for attendance in attendances %}
|
{% for att in attendances %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ attendance.date }}</td>
|
<td>{{ att.date }}</td>
|
||||||
<td>{{ attendance.employee }}</td>
|
<td>{{ att.employee }}</td>
|
||||||
<td>{{ attendance.check_in|default:"-" }}</td>
|
<td>{{ att.check_in|default:"--" }}</td>
|
||||||
<td>{{ attendance.check_out|default:"-" }}</td>
|
<td>{{ att.check_out|default:"--" }}</td>
|
||||||
<td>{{ attendance.notes }}</td>
|
<td class="text-center">
|
||||||
|
<a href="{% url 'hr:attendance_edit' att.pk %}" class="btn btn-sm btn-outline-primary">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center">{% trans "No records found." %}</td>
|
<td colspan="5" class="text-center">{% trans "No attendance records found." %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if is_paginated %}
|
||||||
|
<nav aria-label="Page navigation" class="mt-4">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">{% trans "Previous" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="page-item disabled"><span class="page-link">{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}</span></li>
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">{% trans "Next" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
41
hr/templates/hr/department_form.html
Normal file
41
hr/templates/hr/department_form.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||||
|
<h1 class="h3 mb-0 text-gray-800">{{ title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% for field in form %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">{{ field.label }}</label>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.errors %}
|
||||||
|
<div class="text-danger small">
|
||||||
|
{{ field.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
|
||||||
|
<a href="{% url 'hr:department_list' %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Add bootstrap classes to form fields if not already there
|
||||||
|
document.querySelectorAll('input, select, textarea').forEach(el => {
|
||||||
|
if (!el.classList.contains('form-control') && !el.classList.contains('form-select')) {
|
||||||
|
if (el.tagName === 'SELECT') el.classList.add('form-select');
|
||||||
|
else el.classList.add('form-control');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -3,17 +3,23 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<h1 class="h3 mb-4 text-gray-800">{% trans "Departments" %}</h1>
|
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||||
|
<h1 class="h3 mb-0 text-gray-800">{% trans "Departments" %}</h1>
|
||||||
|
<a href="{% url 'hr:department_add' %}" class="btn btn-primary shadow-sm">
|
||||||
|
<i class="bi bi-plus-lg me-1"></i> {% trans "Add New Department" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card shadow mb-4">
|
<div class="card shadow mb-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered" width="100%" cellspacing="0">
|
<table class="table table-bordered table-hover" width="100%" cellspacing="0">
|
||||||
<thead>
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Name (English)" %}</th>
|
<th>{% trans "Name (English)" %}</th>
|
||||||
<th>{% trans "Name (Arabic)" %}</th>
|
<th>{% trans "Name (Arabic)" %}</th>
|
||||||
<th>{% trans "Employees" %}</th>
|
<th>{% trans "Employees" %}</th>
|
||||||
|
<th class="text-center">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -22,10 +28,15 @@
|
|||||||
<td>{{ department.name_en }}</td>
|
<td>{{ department.name_en }}</td>
|
||||||
<td>{{ department.name_ar }}</td>
|
<td>{{ department.name_ar }}</td>
|
||||||
<td>{{ department.employees.count }}</td>
|
<td>{{ department.employees.count }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<a href="{% url 'hr:department_edit' department.pk %}" class="btn btn-sm btn-outline-primary">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-center">{% trans "No departments found." %}</td>
|
<td colspan="4" class="text-center">{% trans "No departments found." %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -34,4 +45,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -9,8 +9,15 @@ urlpatterns = [
|
|||||||
path('employees/add/', views.EmployeeCreateView.as_view(), name='employee_add'),
|
path('employees/add/', views.EmployeeCreateView.as_view(), name='employee_add'),
|
||||||
path('employees/<int:pk>/', views.EmployeeDetailView.as_view(), name='employee_detail'),
|
path('employees/<int:pk>/', views.EmployeeDetailView.as_view(), name='employee_detail'),
|
||||||
path('employees/<int:pk>/edit/', views.EmployeeUpdateView.as_view(), name='employee_edit'),
|
path('employees/<int:pk>/edit/', views.EmployeeUpdateView.as_view(), name='employee_edit'),
|
||||||
|
|
||||||
path('departments/', views.DepartmentListView.as_view(), name='department_list'),
|
path('departments/', views.DepartmentListView.as_view(), name='department_list'),
|
||||||
|
path('departments/add/', views.DepartmentCreateView.as_view(), name='department_add'),
|
||||||
|
path('departments/<int:pk>/edit/', views.DepartmentUpdateView.as_view(), name='department_edit'),
|
||||||
|
|
||||||
path('attendance/', views.AttendanceListView.as_view(), name='attendance_list'),
|
path('attendance/', views.AttendanceListView.as_view(), name='attendance_list'),
|
||||||
|
path('attendance/add/', views.AttendanceCreateView.as_view(), name='attendance_add'),
|
||||||
|
path('attendance/<int:pk>/edit/', views.AttendanceUpdateView.as_view(), name='attendance_edit'),
|
||||||
|
|
||||||
path('leave/', views.LeaveRequestListView.as_view(), name='leave_list'),
|
path('leave/', views.LeaveRequestListView.as_view(), name='leave_list'),
|
||||||
path('leave/add/', views.LeaveRequestCreateView.as_view(), name='leave_add'),
|
path('leave/add/', views.LeaveRequestCreateView.as_view(), name='leave_add'),
|
||||||
|
|
||||||
|
|||||||
44
hr/views.py
44
hr/views.py
@ -57,12 +57,56 @@ class DepartmentListView(LoginRequiredMixin, ListView):
|
|||||||
template_name = 'hr/department_list.html'
|
template_name = 'hr/department_list.html'
|
||||||
context_object_name = 'departments'
|
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):
|
class AttendanceListView(LoginRequiredMixin, ListView):
|
||||||
model = Attendance
|
model = Attendance
|
||||||
template_name = 'hr/attendance_list.html'
|
template_name = 'hr/attendance_list.html'
|
||||||
context_object_name = 'attendances'
|
context_object_name = 'attendances'
|
||||||
paginate_by = 50
|
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):
|
class LeaveRequestListView(LoginRequiredMixin, ListView):
|
||||||
model = LeaveRequest
|
model = LeaveRequest
|
||||||
template_name = 'hr/leave_list.html'
|
template_name = 'hr/leave_list.html'
|
||||||
|
|||||||
49
patch_base_html.py
Normal file
49
patch_base_html.py
Normal file
@ -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 %}
|
||||||
|
<div class="p-3">
|
||||||
|
<button type="button" id="sidebarCollapse" class="btn btn-light shadow-sm">
|
||||||
|
<i class="bi bi-list fs-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}"""
|
||||||
|
|
||||||
|
replace_text = """ {% if user.is_authenticated %}
|
||||||
|
<div class="p-3 d-flex justify-content-between align-items-center">
|
||||||
|
<button type="button" id="sidebarCollapse" class="btn btn-light shadow-sm">
|
||||||
|
<i class="bi bi-list fs-5"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="language-switcher">
|
||||||
|
<form action="{% url 'set_language' %}" method="post" class="d-flex align-items-center">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="next" value="{{ request.get_full_path|default:'/' }}">
|
||||||
|
<i class="bi bi-globe2 me-2 text-muted"></i>
|
||||||
|
<select name="language" class="form-select form-select-sm shadow-sm border-0" style="width: auto; background-color: #f8f9fa;" onchange="this.form.submit()">
|
||||||
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
{% get_available_languages as LANGUAGES %}
|
||||||
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
|
{% for language in languages %}
|
||||||
|
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
|
||||||
|
{{ language.name_local }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% 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.")
|
||||||
34
patch_pos_view.py
Normal file
34
patch_pos_view.py
Normal file
@ -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])
|
||||||
Loading…
x
Reference in New Issue
Block a user