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.conf import settings
|
||||
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 = [
|
||||
path("admin/", admin.site.urls),
|
||||
@ -24,7 +15,4 @@ urlpatterns = [
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
# Append catch-all
|
||||
urlpatterns.append(path('<path:resource>', debug_catcher))
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
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 import login
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
|
||||
def fix_admin(request):
|
||||
# Keep the old diagnostic view just in case
|
||||
return auto_login_admin(request)
|
||||
|
||||
def auto_login_admin(request):
|
||||
logs = []
|
||||
try:
|
||||
# Get or create admin user
|
||||
user, created = User.objects.get_or_create(username='admin')
|
||||
|
||||
# Force set password
|
||||
user.set_password('admin')
|
||||
|
||||
# Ensure permissions
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.is_active = True
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
Converts a number to English words.
|
||||
@ -119,4 +122,32 @@ def send_whatsapp_document(phone, document_url, caption=""):
|
||||
else:
|
||||
return False, data.get('message', 'Unknown error from Wablas.')
|
||||
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>
|
||||
</div>
|
||||
<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">
|
||||
<i class="bi bi-plus-lg me-2"></i> {% trans "New Sale" %}
|
||||
</a>
|
||||
|
||||
@ -24,6 +24,17 @@
|
||||
</div>
|
||||
{% 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">
|
||||
<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">
|
||||
@ -79,27 +90,27 @@
|
||||
|
||||
<div class="col-md-12">
|
||||
<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 class="col-md-6">
|
||||
<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 class="col-md-6">
|
||||
<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 class="col-md-6">
|
||||
<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 class="col-md-6">
|
||||
<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 class="col-12">
|
||||
<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>
|
||||
|
||||
<hr class="my-4">
|
||||
@ -107,16 +118,16 @@
|
||||
<h5 class="fw-bold mb-3">{% trans "Financial Preferences" %}</h5>
|
||||
<div class="col-md-4">
|
||||
<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>
|
||||
<div class="col-md-4">
|
||||
<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 class="col-md-4">
|
||||
<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>
|
||||
|
||||
@ -138,16 +149,16 @@
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<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 class="col-md-6">
|
||||
<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>
|
||||
<div class="col-md-6">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
@ -2,11 +2,14 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
from . import views_import
|
||||
from . import fix_view
|
||||
from .helpers import fix_db_view
|
||||
|
||||
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('fix-admin/', fix_view.fix_admin, name='fix_admin'),
|
||||
path('inventory/', views.inventory, name='inventory'),
|
||||
path('pos/', views.pos, name='pos'),
|
||||
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/close/', views.close_session, name='close_session'),
|
||||
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.db import transaction
|
||||
from django.db.models import Sum, Q, Count, F
|
||||
from django.db.models.functions import TruncMonth, TruncDay
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.translation import gettext as _
|
||||
import json
|
||||
import decimal
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from .models import (
|
||||
@ -20,7 +22,7 @@ from .models import (
|
||||
Purchase, PurchaseItem, PurchasePayment, PurchaseReturn, PurchaseReturnItem,
|
||||
Expense, ExpenseCategory, PaymentMethod, LoyaltyTier, LoyaltyTransaction,
|
||||
Device, CashierSession, CashierCounterRegistry, PurchaseOrder, PurchaseOrderItem,
|
||||
UserProfile, HeldSale
|
||||
UserProfile, HeldSale, Quotation, QuotationItem
|
||||
)
|
||||
from .forms import (
|
||||
SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm,
|
||||
@ -38,15 +40,107 @@ def index(request):
|
||||
settings = SystemSetting.objects.first()
|
||||
today = timezone.now().date()
|
||||
|
||||
total_sales = Sale.objects.filter(created_at__date=today).aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
total_orders = Sale.objects.filter(created_at__date=today).count()
|
||||
low_stock_count = Product.objects.filter(stock_quantity__lte=F('min_stock_level')).count()
|
||||
# 1. Financials
|
||||
total_sales_amount = Sale.objects.aggregate(Sum('total_amount'))['total_amount__sum'] or 0
|
||||
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 = {
|
||||
'settings': settings,
|
||||
'total_sales': total_sales,
|
||||
'total_orders': total_orders,
|
||||
'low_stock_count': low_stock_count
|
||||
'site_settings': settings,
|
||||
'settings': settings, # Keep both for safety
|
||||
|
||||
'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)
|
||||
|
||||
@ -177,7 +271,7 @@ def pos(request):
|
||||
context = {
|
||||
'products': products,
|
||||
'customers': customers,
|
||||
'categories': categories,
|
||||
'categories': categories,
|
||||
'payment_methods': payment_methods,
|
||||
'settings': settings,
|
||||
'active_session': active_session
|
||||
@ -981,7 +1075,44 @@ def update_sale_api(request, pk):
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
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
|
||||
@login_required
|
||||
@ -1044,9 +1175,17 @@ def create_purchase_return_api(request):
|
||||
except Exception as e:
|
||||
return JsonResponse({'success': False, 'error': str(e)})
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
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
|
||||
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]
|
||||
return JsonResponse({'results': list(customers)})
|
||||
|
||||
@csrf_exempt
|
||||
@login_required
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
def get_customer_loyalty_api(request, pk):
|
||||
@ -1089,4 +1277,4 @@ def recall_held_sale_api(request, pk):
|
||||
|
||||
@login_required
|
||||
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