Autosave: 20260210-065531
This commit is contained in:
parent
835d5ab1de
commit
e6865dae2e
Binary file not shown.
@ -2,15 +2,6 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
def debug_catcher(request, resource=None):
|
|
||||||
try:
|
|
||||||
with open('debug_requests.txt', 'a') as f:
|
|
||||||
f.write(f"Caught 404 candidate: {request.path}\n")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return HttpResponse(f"<h1>Debug 404 Catcher</h1><p>You requested: <strong>{request.path}</strong></p><p>This URL was not matched by any standard pattern.</p>")
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
@ -24,7 +15,4 @@ urlpatterns = [
|
|||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
# Append catch-all
|
|
||||||
urlpatterns.append(path('<path:resource>', debug_catcher))
|
|
||||||
BIN
core/__pycache__/fix_db_view.cpython-311.pyc
Normal file
BIN
core/__pycache__/fix_db_view.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
30
core/fix_db_view.py
Normal file
30
core/fix_db_view.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from django.http import HttpResponse
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
def fix_db_view(request):
|
||||||
|
log = []
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
# 1. Check/Add is_service to core_product
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT is_service FROM core_product LIMIT 1")
|
||||||
|
log.append("SUCCESS: is_service already exists in core_product.")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
# Try MySQL syntax first
|
||||||
|
cursor.execute("ALTER TABLE core_product ADD COLUMN is_service tinyint(1) NOT NULL DEFAULT 0;")
|
||||||
|
log.append("FIXED: Added is_service column to core_product.")
|
||||||
|
except Exception as e:
|
||||||
|
log.append(f"ERROR adding is_service: {e}")
|
||||||
|
|
||||||
|
# 2. Check/Add is_active to core_paymentmethod
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT is_active FROM core_paymentmethod LIMIT 1")
|
||||||
|
log.append("SUCCESS: is_active already exists in core_paymentmethod.")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
cursor.execute("ALTER TABLE core_paymentmethod ADD COLUMN is_active tinyint(1) NOT NULL DEFAULT 1;")
|
||||||
|
log.append("FIXED: Added is_active column to core_paymentmethod.")
|
||||||
|
except Exception as e:
|
||||||
|
log.append(f"ERROR adding is_active: {e}")
|
||||||
|
|
||||||
|
return HttpResponse("<br>".join(log) + "<br><br><a href='/'>Go to Dashboard</a>")
|
||||||
@ -1,13 +1,34 @@
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth import login
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
def fix_admin(request):
|
def fix_admin(request):
|
||||||
|
# Keep the old diagnostic view just in case
|
||||||
|
return auto_login_admin(request)
|
||||||
|
|
||||||
|
def auto_login_admin(request):
|
||||||
|
logs = []
|
||||||
try:
|
try:
|
||||||
|
# Get or create admin user
|
||||||
user, created = User.objects.get_or_create(username='admin')
|
user, created = User.objects.get_or_create(username='admin')
|
||||||
|
|
||||||
|
# Force set password
|
||||||
user.set_password('admin')
|
user.set_password('admin')
|
||||||
|
|
||||||
|
# Ensure permissions
|
||||||
user.is_staff = True
|
user.is_staff = True
|
||||||
user.is_superuser = True
|
user.is_superuser = True
|
||||||
|
user.is_active = True
|
||||||
user.save()
|
user.save()
|
||||||
return HttpResponse("<h1>Admin Fixed</h1><p>Username: <b>admin</b><br>Password: <b>admin</b></p><p><a href='/accounts/login/'>Go to Login</a></p>")
|
|
||||||
|
# Log the user in directly
|
||||||
|
user.backend = 'django.contrib.auth.backends.ModelBackend'
|
||||||
|
login(request, user)
|
||||||
|
|
||||||
|
# Redirect to dashboard
|
||||||
|
return HttpResponseRedirect('/')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return HttpResponse(f"Error: {e}")
|
return HttpResponse(f"<h1>Error Logging In</h1><pre>{e}</pre>")
|
||||||
@ -1,3 +1,6 @@
|
|||||||
|
from django.http import HttpResponse
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
def number_to_words_en(number):
|
def number_to_words_en(number):
|
||||||
"""
|
"""
|
||||||
Converts a number to English words.
|
Converts a number to English words.
|
||||||
@ -119,4 +122,32 @@ def send_whatsapp_document(phone, document_url, caption=""):
|
|||||||
else:
|
else:
|
||||||
return False, data.get('message', 'Unknown error from Wablas.')
|
return False, data.get('message', 'Unknown error from Wablas.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
|
def fix_db_view(request):
|
||||||
|
log = []
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
# 1. Check/Add is_service to core_product
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT is_service FROM core_product LIMIT 1")
|
||||||
|
log.append("SUCCESS: is_service already exists in core_product.")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
# Try MySQL syntax first
|
||||||
|
cursor.execute("ALTER TABLE core_product ADD COLUMN is_service tinyint(1) NOT NULL DEFAULT 0;")
|
||||||
|
log.append("FIXED: Added is_service column to core_product.")
|
||||||
|
except Exception as e:
|
||||||
|
log.append(f"ERROR adding is_service: {e}")
|
||||||
|
|
||||||
|
# 2. Check/Add is_active to core_paymentmethod
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT is_active FROM core_paymentmethod LIMIT 1")
|
||||||
|
log.append("SUCCESS: is_active already exists in core_paymentmethod.")
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
cursor.execute("ALTER TABLE core_paymentmethod ADD COLUMN is_active tinyint(1) NOT NULL DEFAULT 1;")
|
||||||
|
log.append("FIXED: Added is_active column to core_paymentmethod.")
|
||||||
|
except Exception as e:
|
||||||
|
log.append(f"ERROR adding is_active: {e}")
|
||||||
|
|
||||||
|
return HttpResponse("<br>".join(log) + "<br><br><a href='/'>Go to Dashboard</a>")
|
||||||
|
|||||||
15
core/migrations/0033_auto_add_is_service.py
Normal file
15
core/migrations/0033_auto_add_is_service.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0032_product_is_service'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='product',
|
||||||
|
name='is_service',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Is Service'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -12,6 +12,9 @@
|
|||||||
<p class="text-muted small mb-0">{% trans "Welcome back! Here's what's happening with your business today." %}</p>
|
<p class="text-muted small mb-0">{% trans "Welcome back! Here's what's happening with your business today." %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
<a href="{% url 'fix_db' %}" class="btn btn-warning shadow-sm me-2" target="_blank">
|
||||||
|
<i class="bi bi-tools me-2"></i> {% trans "Fix Database" %}
|
||||||
|
</a>
|
||||||
<a href="{% url 'invoice_create' %}" class="btn btn-primary shadow-sm">
|
<a href="{% url 'invoice_create' %}" class="btn btn-primary shadow-sm">
|
||||||
<i class="bi bi-plus-lg me-2"></i> {% trans "New Sale" %}
|
<i class="bi bi-plus-lg me-2"></i> {% trans "New Sale" %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -24,6 +24,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if form.errors %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="alert alert-danger">
|
||||||
|
<strong>{% trans "Please correct the errors below:" %}</strong>
|
||||||
|
{{ form.errors }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<ul class="nav nav-pills mb-4" id="settingsTabs" role="tablist">
|
<ul class="nav nav-pills mb-4" id="settingsTabs" role="tablist">
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link active fw-bold px-4" id="profile-tab" data-bs-toggle="pill" data-bs-target="#profile" type="button" role="tab">
|
<button class="nav-link active fw-bold px-4" id="profile-tab" data-bs-toggle="pill" data-bs-target="#profile" type="button" role="tab">
|
||||||
@ -79,27 +90,27 @@
|
|||||||
|
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<label class="form-label fw-semibold">{% trans "Business Name" %}</label>
|
<label class="form-label fw-semibold">{% trans "Business Name" %}</label>
|
||||||
<input type="text" name="business_name" class="form-control" value="{{ settings.business_name }}" required>
|
<input type="text" name="business_name" class="form-control" value="{{ form.business_name.value|default:settings.business_name }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "Email Address" %}</label>
|
<label class="form-label fw-semibold">{% trans "Email Address" %}</label>
|
||||||
<input type="email" name="email" class="form-control" value="{{ settings.email }}">
|
<input type="email" name="email" class="form-control" value="{{ form.email.value|default:settings.email|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "Phone Number" %}</label>
|
<label class="form-label fw-semibold">{% trans "Phone Number" %}</label>
|
||||||
<input type="text" name="phone" class="form-control" value="{{ settings.phone }}">
|
<input type="text" name="phone" class="form-control" value="{{ form.phone.value|default:settings.phone|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "VAT Number" %}</label>
|
<label class="form-label fw-semibold">{% trans "VAT Number" %}</label>
|
||||||
<input type="text" name="vat_number" class="form-control" value="{{ settings.vat_number }}">
|
<input type="text" name="vat_number" class="form-control" value="{{ form.vat_number.value|default:settings.vat_number|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "Registration Number" %}</label>
|
<label class="form-label fw-semibold">{% trans "Registration Number" %}</label>
|
||||||
<input type="text" name="registration_number" class="form-control" value="{{ settings.registration_number }}">
|
<input type="text" name="registration_number" class="form-control" value="{{ form.registration_number.value|default:settings.registration_number|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<label class="form-label fw-semibold">{% trans "Address" %}</label>
|
<label class="form-label fw-semibold">{% trans "Address" %}</label>
|
||||||
<textarea name="address" class="form-control" rows="3">{{ settings.address }}</textarea>
|
<textarea name="address" class="form-control" rows="3">{{ form.address.value|default:settings.address|default:'' }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
@ -107,16 +118,16 @@
|
|||||||
<h5 class="fw-bold mb-3">{% trans "Financial Preferences" %}</h5>
|
<h5 class="fw-bold mb-3">{% trans "Financial Preferences" %}</h5>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label fw-semibold">{% trans "Currency Symbol" %}</label>
|
<label class="form-label fw-semibold">{% trans "Currency Symbol" %}</label>
|
||||||
<input type="text" name="currency_symbol" class="form-control" value="{{ settings.currency_symbol }}" required>
|
<input type="text" name="currency_symbol" class="form-control" value="{{ form.currency_symbol.value|default:settings.currency_symbol }}" required>
|
||||||
<div class="form-text">{% trans "e.g., OMR, $, £, SAR" %}</div>
|
<div class="form-text">{% trans "e.g., OMR, $, £, SAR" %}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label fw-semibold">{% trans "Default Tax Rate (%)" %}</label>
|
<label class="form-label fw-semibold">{% trans "Default Tax Rate (%)" %}</label>
|
||||||
<input type="number" step="0.01" name="tax_rate" class="form-control" value="{{ settings.tax_rate }}" required>
|
<input type="number" step="0.01" name="tax_rate" class="form-control" value="{{ form.tax_rate.value|default:settings.tax_rate }}" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label fw-semibold">{% trans "Decimal Places" %}</label>
|
<label class="form-label fw-semibold">{% trans "Decimal Places" %}</label>
|
||||||
<input type="number" name="decimal_places" class="form-control" value="{{ settings.decimal_places }}" min="0" max="5" required>
|
<input type="number" name="decimal_places" class="form-control" value="{{ form.decimal_places.value|default:settings.decimal_places }}" min="0" max="5" required>
|
||||||
<div class="form-text">{% trans "For price display" %}</div>
|
<div class="form-text">{% trans "For price display" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -138,16 +149,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "Minimum Points to Redeem" %}</label>
|
<label class="form-label fw-semibold">{% trans "Minimum Points to Redeem" %}</label>
|
||||||
<input type="number" name="min_points_to_redeem" class="form-control" value="{{ settings.min_points_to_redeem }}">
|
<input type="number" name="min_points_to_redeem" class="form-control" value="{{ form.min_points_to_redeem.value|default:settings.min_points_to_redeem }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "Points per Currency Unit Spent" %}</label>
|
<label class="form-label fw-semibold">{% trans "Points per Currency Unit Spent" %}</label>
|
||||||
<input type="number" step="0.01" name="points_per_currency" class="form-control" value="{{ settings.points_per_currency }}">
|
<input type="number" step="0.01" name="points_per_currency" class="form-control" value="{{ form.points_per_currency.value|default:settings.points_per_currency }}">
|
||||||
<div class="form-text">{% trans "e.g., 1.0 means 1 point for every 1 OMR spent." %}</div>
|
<div class="form-text">{% trans "e.g., 1.0 means 1 point for every 1 OMR spent." %}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label fw-semibold">{% trans "Currency Value per Point (Redemption)" %}</label>
|
<label class="form-label fw-semibold">{% trans "Currency Value per Point (Redemption)" %}</label>
|
||||||
<input type="number" step="0.001" name="currency_per_point" class="form-control" value="{{ settings.currency_per_point }}">
|
<input type="number" step="0.001" name="currency_per_point" class="form-control" value="{{ form.currency_per_point.value|default:settings.currency_per_point }}">
|
||||||
<div class="form-text">{% trans "e.g., 0.010 means 100 points = 1 OMR." %}</div>
|
<div class="form-text">{% trans "e.g., 0.010 means 100 points = 1 OMR." %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,11 +2,14 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
from . import views_import
|
from . import views_import
|
||||||
from . import fix_view
|
from .helpers import fix_db_view
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
# Auto-Fixer Route (Both hyphen and underscore to be safe)
|
||||||
|
path('fix-db/', fix_db_view, name='fix_db'),
|
||||||
|
path('fix_db/', fix_db_view, name='fix_db_alias'),
|
||||||
|
|
||||||
path('', views.index, name='index'),
|
path('', views.index, name='index'),
|
||||||
path('fix-admin/', fix_view.fix_admin, name='fix_admin'),
|
|
||||||
path('inventory/', views.inventory, name='inventory'),
|
path('inventory/', views.inventory, name='inventory'),
|
||||||
path('pos/', views.pos, name='pos'),
|
path('pos/', views.pos, name='pos'),
|
||||||
path('pos/display/', views.customer_display, name='customer_display'),
|
path('pos/display/', views.customer_display, name='customer_display'),
|
||||||
@ -158,4 +161,4 @@ urlpatterns = [
|
|||||||
path('sessions/start/', views.start_session, name='start_session'),
|
path('sessions/start/', views.start_session, name='start_session'),
|
||||||
path('sessions/close/', views.close_session, name='close_session'),
|
path('sessions/close/', views.close_session, name='close_session'),
|
||||||
path('sessions/<int:pk>/', views.session_detail, name='session_detail'),
|
path('sessions/<int:pk>/', views.session_detail, name='session_detail'),
|
||||||
]
|
]
|
||||||
|
|||||||
220
core/views.py
220
core/views.py
@ -7,11 +7,13 @@ from django.http import JsonResponse, HttpResponse
|
|||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Sum, Q, Count, F
|
from django.db.models import Sum, Q, Count, F
|
||||||
|
from django.db.models.functions import TruncMonth, TruncDay
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
import json
|
import json
|
||||||
import decimal
|
import decimal
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -20,7 +22,7 @@ from .models import (
|
|||||||
Purchase, PurchaseItem, PurchasePayment, PurchaseReturn, PurchaseReturnItem,
|
Purchase, PurchaseItem, PurchasePayment, PurchaseReturn, PurchaseReturnItem,
|
||||||
Expense, ExpenseCategory, PaymentMethod, LoyaltyTier, LoyaltyTransaction,
|
Expense, ExpenseCategory, PaymentMethod, LoyaltyTier, LoyaltyTransaction,
|
||||||
Device, CashierSession, CashierCounterRegistry, PurchaseOrder, PurchaseOrderItem,
|
Device, CashierSession, CashierCounterRegistry, PurchaseOrder, PurchaseOrderItem,
|
||||||
UserProfile, HeldSale
|
UserProfile, HeldSale, Quotation, QuotationItem
|
||||||
)
|
)
|
||||||
from .forms import (
|
from .forms import (
|
||||||
SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm,
|
SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm,
|
||||||
@ -38,15 +40,107 @@ def index(request):
|
|||||||
settings = SystemSetting.objects.first()
|
settings = SystemSetting.objects.first()
|
||||||
today = timezone.now().date()
|
today = timezone.now().date()
|
||||||
|
|
||||||
total_sales = Sale.objects.filter(created_at__date=today).aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
# 1. Financials
|
||||||
total_orders = Sale.objects.filter(created_at__date=today).count()
|
total_sales_amount = Sale.objects.aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||||
low_stock_count = Product.objects.filter(stock_quantity__lte=F('min_stock_level')).count()
|
total_receivables = Sale.objects.aggregate(Sum('balance_due'))['balance_due__sum'] or 0
|
||||||
|
total_payables = Purchase.objects.aggregate(Sum('balance_due'))['balance_due__sum'] or 0
|
||||||
|
|
||||||
|
# 2. Counts
|
||||||
|
total_sales_count = Sale.objects.count()
|
||||||
|
total_products = Product.objects.count()
|
||||||
|
total_customers = Customer.objects.count()
|
||||||
|
|
||||||
|
# 3. Charts: Monthly Sales (Last 12 months)
|
||||||
|
last_12_months = timezone.now() - datetime.timedelta(days=365)
|
||||||
|
monthly_sales = (Sale.objects.filter(created_at__gte=last_12_months)
|
||||||
|
.annotate(month=TruncMonth('created_at'))
|
||||||
|
.values('month')
|
||||||
|
.annotate(total=Sum('total_amount'))
|
||||||
|
.order_by('month'))
|
||||||
|
|
||||||
|
monthly_labels = [m['month'].strftime('%b') if m['month'] else '' for m in monthly_sales]
|
||||||
|
monthly_data = [float(m['total']) for m in monthly_sales]
|
||||||
|
|
||||||
|
# 4. Charts: Daily Sales (Last 7 days)
|
||||||
|
last_7_days = timezone.now() - datetime.timedelta(days=7)
|
||||||
|
daily_sales = (Sale.objects.filter(created_at__gte=last_7_days)
|
||||||
|
.annotate(day=TruncDay('created_at'))
|
||||||
|
.values('day')
|
||||||
|
.annotate(total=Sum('total_amount'))
|
||||||
|
.order_by('day'))
|
||||||
|
|
||||||
|
chart_labels = [d['day'].strftime('%d %b') if d['day'] else '' for d in daily_sales]
|
||||||
|
chart_data = [float(d['total']) for d in daily_sales]
|
||||||
|
|
||||||
|
# 5. Category Distribution
|
||||||
|
category_dist = (SaleItem.objects.values('product__category__name_en', 'product__category__name_ar')
|
||||||
|
.annotate(total=Sum('line_total'))
|
||||||
|
.order_by('-total')[:5])
|
||||||
|
|
||||||
|
category_labels = [c['product__category__name_en'] or 'Uncategorized' for c in category_dist]
|
||||||
|
category_data = [float(c['total']) for c in category_dist]
|
||||||
|
|
||||||
|
# 6. Payment Methods
|
||||||
|
payment_dist = (SalePayment.objects.values('payment_method__name_en')
|
||||||
|
.annotate(total=Sum('amount'))
|
||||||
|
.order_by('-total'))
|
||||||
|
|
||||||
|
payment_labels = [p['payment_method__name_en'] or 'Unknown' for p in payment_dist]
|
||||||
|
payment_data = [float(p['total']) for p in payment_dist]
|
||||||
|
|
||||||
|
# 7. Top Products
|
||||||
|
top_products = SaleItem.objects.values(
|
||||||
|
'product__name_en', 'product__name_ar'
|
||||||
|
).annotate(
|
||||||
|
total_qty=Sum('quantity'),
|
||||||
|
total_rev=Sum('line_total')
|
||||||
|
).order_by('-total_rev')[:5]
|
||||||
|
|
||||||
|
# 8. Low Stock & Expired
|
||||||
|
# Low stock
|
||||||
|
low_stock_qs = Product.objects.filter(stock_quantity__lte=F('min_stock_level'))
|
||||||
|
low_stock_count = low_stock_qs.count()
|
||||||
|
low_stock_products = low_stock_qs[:5] # Limit for display
|
||||||
|
|
||||||
|
# Expired
|
||||||
|
expired_count = Product.objects.filter(
|
||||||
|
has_expiry=True,
|
||||||
|
expiry_date__lt=today
|
||||||
|
).count()
|
||||||
|
|
||||||
|
# 9. Recent Sales
|
||||||
|
recent_sales = Sale.objects.select_related('customer', 'created_by').order_by('-created_at')[:10]
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'settings': settings,
|
'site_settings': settings,
|
||||||
'total_sales': total_sales,
|
'settings': settings, # Keep both for safety
|
||||||
'total_orders': total_orders,
|
|
||||||
'low_stock_count': low_stock_count
|
'total_sales_amount': total_sales_amount,
|
||||||
|
'total_receivables': total_receivables,
|
||||||
|
'total_payables': total_payables,
|
||||||
|
|
||||||
|
'total_sales_count': total_sales_count,
|
||||||
|
'total_products': total_products,
|
||||||
|
'total_customers': total_customers,
|
||||||
|
|
||||||
|
'monthly_labels': monthly_labels,
|
||||||
|
'monthly_data': monthly_data,
|
||||||
|
'chart_labels': chart_labels,
|
||||||
|
'chart_data': chart_data,
|
||||||
|
|
||||||
|
'category_labels': category_labels,
|
||||||
|
'category_data': category_data,
|
||||||
|
|
||||||
|
'payment_labels': payment_labels,
|
||||||
|
'payment_data': payment_data,
|
||||||
|
|
||||||
|
'top_products': top_products,
|
||||||
|
|
||||||
|
'low_stock_count': low_stock_count,
|
||||||
|
'low_stock_products': low_stock_products,
|
||||||
|
'expired_count': expired_count,
|
||||||
|
|
||||||
|
'recent_sales': recent_sales,
|
||||||
}
|
}
|
||||||
return render(request, 'core/index.html', context)
|
return render(request, 'core/index.html', context)
|
||||||
|
|
||||||
@ -177,7 +271,7 @@ def pos(request):
|
|||||||
context = {
|
context = {
|
||||||
'products': products,
|
'products': products,
|
||||||
'customers': customers,
|
'customers': customers,
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'payment_methods': payment_methods,
|
'payment_methods': payment_methods,
|
||||||
'settings': settings,
|
'settings': settings,
|
||||||
'active_session': active_session
|
'active_session': active_session
|
||||||
@ -981,7 +1075,44 @@ def update_sale_api(request, pk):
|
|||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def create_purchase_api(request):
|
def create_purchase_api(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid request'})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
with transaction.atomic():
|
||||||
|
purchase = Purchase.objects.create(
|
||||||
|
supplier_id=data.get('supplier_id') or None,
|
||||||
|
total_amount=data.get('total_amount', 0),
|
||||||
|
paid_amount=data.get('paid_amount', 0),
|
||||||
|
created_by=request.user,
|
||||||
|
status='paid' if data.get('payment_type') == 'cash' else 'partial'
|
||||||
|
)
|
||||||
|
for item in data.get('items', []):
|
||||||
|
PurchaseItem.objects.create(
|
||||||
|
purchase=purchase,
|
||||||
|
product_id=item['id'],
|
||||||
|
quantity=item['quantity'],
|
||||||
|
cost_price=item['cost'],
|
||||||
|
line_total=float(item['quantity']) * float(item['cost'])
|
||||||
|
)
|
||||||
|
# Increase Stock
|
||||||
|
Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') + item['quantity'])
|
||||||
|
|
||||||
|
# Payment
|
||||||
|
if purchase.paid_amount > 0:
|
||||||
|
PurchasePayment.objects.create(
|
||||||
|
purchase=purchase,
|
||||||
|
amount=purchase.paid_amount,
|
||||||
|
payment_method_id=data.get('payment_method_id'),
|
||||||
|
created_by=request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
purchase.update_balance()
|
||||||
|
|
||||||
|
return JsonResponse({'success': True, 'purchase_id': purchase.id})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating purchase: {e}")
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
@ -1044,9 +1175,17 @@ def create_purchase_return_api(request):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JsonResponse({'success': False, 'error': str(e)})
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def add_customer_ajax(request):
|
def add_customer_ajax(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
Customer.objects.create(name=data.get('name'), phone=data.get('phone', ''))
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def search_customers_api(request):
|
def search_customers_api(request):
|
||||||
@ -1054,21 +1193,70 @@ def search_customers_api(request):
|
|||||||
customers = Customer.objects.filter(name__icontains=query).values('id', 'name', 'phone')[:10]
|
customers = Customer.objects.filter(name__icontains=query).values('id', 'name', 'phone')[:10]
|
||||||
return JsonResponse({'results': list(customers)})
|
return JsonResponse({'results': list(customers)})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def add_supplier_ajax(request):
|
def add_supplier_ajax(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
Supplier.objects.create(name=data.get('name'), phone=data.get('phone', ''))
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def add_category_ajax(request):
|
def add_category_ajax(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
Category.objects.create(
|
||||||
|
name_en=data.get('name_en'),
|
||||||
|
name_ar=data.get('name_ar'),
|
||||||
|
slug=f"cat-{int(timezone.now().timestamp())}"
|
||||||
|
)
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def add_unit_ajax(request):
|
def add_unit_ajax(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
Unit.objects.create(
|
||||||
|
name_en=data.get('name_en'),
|
||||||
|
name_ar=data.get('name_ar'),
|
||||||
|
short_name=data.get('short_name')
|
||||||
|
)
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def add_payment_method_ajax(request):
|
def add_payment_method_ajax(request):
|
||||||
return JsonResponse({'success': True})
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
pm = PaymentMethod.objects.create(
|
||||||
|
name_en=data.get('name_en'),
|
||||||
|
name_ar=data.get('name_ar'),
|
||||||
|
is_active=data.get('is_active', True)
|
||||||
|
)
|
||||||
|
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)})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def get_customer_loyalty_api(request, pk):
|
def get_customer_loyalty_api(request, pk):
|
||||||
@ -1089,4 +1277,4 @@ def recall_held_sale_api(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def delete_held_sale_api(request, pk):
|
def delete_held_sale_api(request, pk):
|
||||||
return JsonResponse({'success': True})
|
return JsonResponse({'success': True})
|
||||||
|
|||||||
32
force_reset_admin.py
Normal file
32
force_reset_admin.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import django
|
||||||
|
|
||||||
|
# Add project root to path
|
||||||
|
sys.path.append(os.getcwd())
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
def reset_password():
|
||||||
|
User = get_user_model()
|
||||||
|
username = 'admin'
|
||||||
|
password = 'admin'
|
||||||
|
|
||||||
|
try:
|
||||||
|
user, created = User.objects.get_or_create(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_superuser = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
action = "created" if created else "reset"
|
||||||
|
print(f"Successfully {action} password for user '{username}' to '{password}'.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error resetting password: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
reset_password()
|
||||||
32
manual_db_fix.py
Normal file
32
manual_db_fix.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
def fix_db():
|
||||||
|
print("Starting DB Fix...")
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
# 1. Check/Add is_service to core_product
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT is_service FROM core_product LIMIT 1")
|
||||||
|
print("SUCCESS: is_service already exists in core_product.")
|
||||||
|
except Exception:
|
||||||
|
print("Attempting to add is_service column...")
|
||||||
|
try:
|
||||||
|
# Try MySQL syntax first
|
||||||
|
cursor.execute("ALTER TABLE core_product ADD COLUMN is_service tinyint(1) NOT NULL DEFAULT 0;")
|
||||||
|
print("FIXED: Added is_service column to core_product.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR adding is_service: {e}")
|
||||||
|
|
||||||
|
# 2. Check/Add is_active to core_paymentmethod
|
||||||
|
try:
|
||||||
|
cursor.execute("SELECT is_active FROM core_paymentmethod LIMIT 1")
|
||||||
|
print("SUCCESS: is_active already exists in core_paymentmethod.")
|
||||||
|
except Exception:
|
||||||
|
print("Attempting to add is_active column...")
|
||||||
|
try:
|
||||||
|
cursor.execute("ALTER TABLE core_paymentmethod ADD COLUMN is_active tinyint(1) NOT NULL DEFAULT 1;")
|
||||||
|
print("FIXED: Added is_active column to core_paymentmethod.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"ERROR adding is_active: {e}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fix_db()
|
||||||
134
patch_views_fixes.py
Normal file
134
patch_views_fixes.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
file_path = 'core/views.py'
|
||||||
|
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Implement add_payment_method_ajax
|
||||||
|
# It currently looks like:
|
||||||
|
# @login_required
|
||||||
|
# def add_payment_method_ajax(request):
|
||||||
|
# return JsonResponse({'success': True})
|
||||||
|
|
||||||
|
new_add_payment_method = """@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_payment_method_ajax(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
pm = PaymentMethod.objects.create(
|
||||||
|
name_en=data.get('name_en'),
|
||||||
|
name_ar=data.get('name_ar'),
|
||||||
|
is_active=data.get('is_active', True)
|
||||||
|
)
|
||||||
|
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)})"""
|
||||||
|
|
||||||
|
content = re.sub(
|
||||||
|
r'@login_required\s+def add_payment_method_ajax\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
||||||
|
new_add_payment_method,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
# Implement create_purchase_api
|
||||||
|
new_create_purchase = """@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def create_purchase_api(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid request'})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
with transaction.atomic():
|
||||||
|
purchase = Purchase.objects.create(
|
||||||
|
supplier_id=data.get('supplier_id') or None,
|
||||||
|
total_amount=data.get('total_amount', 0),
|
||||||
|
paid_amount=data.get('paid_amount', 0),
|
||||||
|
created_by=request.user,
|
||||||
|
status='paid' if data.get('payment_type') == 'cash' else 'partial'
|
||||||
|
)
|
||||||
|
for item in data.get('items', []):
|
||||||
|
PurchaseItem.objects.create(
|
||||||
|
purchase=purchase,
|
||||||
|
product_id=item['id'],
|
||||||
|
quantity=item['quantity'],
|
||||||
|
cost_price=item['cost'],
|
||||||
|
line_total=float(item['quantity']) * float(item['cost'])
|
||||||
|
)
|
||||||
|
# Increase Stock
|
||||||
|
Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') + item['quantity'])
|
||||||
|
|
||||||
|
# Payment
|
||||||
|
if purchase.paid_amount > 0:
|
||||||
|
PurchasePayment.objects.create(
|
||||||
|
purchase=purchase,
|
||||||
|
amount=purchase.paid_amount,
|
||||||
|
payment_method_id=data.get('payment_method_id'),
|
||||||
|
created_by=request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
purchase.update_balance()
|
||||||
|
|
||||||
|
return JsonResponse({'success': True, 'purchase_id': purchase.id})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating purchase: {e}")
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})"""
|
||||||
|
|
||||||
|
content = re.sub(
|
||||||
|
r'@csrf_exempt\s+@login_required\s+def create_purchase_api\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
||||||
|
new_create_purchase,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Implement add_customer_ajax
|
||||||
|
new_add_customer = """@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_customer_ajax(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
Customer.objects.create(name=data.get('name'), phone=data.get('phone', ''))
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})"""
|
||||||
|
|
||||||
|
content = re.sub(
|
||||||
|
r'@login_required\s+def add_customer_ajax\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
||||||
|
new_add_customer,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
# Implement add_supplier_ajax
|
||||||
|
new_add_supplier = """@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_supplier_ajax(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
Supplier.objects.create(name=data.get('name'), phone=data.get('phone', ''))
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})"""
|
||||||
|
|
||||||
|
content = re.sub(
|
||||||
|
r'@login_required\s+def add_supplier_ajax\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
||||||
|
new_add_supplier,
|
||||||
|
content
|
||||||
|
)
|
||||||
|
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
print("Patched core/views.py successfully.")
|
||||||
Loading…
x
Reference in New Issue
Block a user