Autosave: 20260210-025451
This commit is contained in:
parent
d339a98349
commit
c851649b1a
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from .models import CashierSession
|
from .models import CashierSession, SystemSetting, Product, Category, Unit, Supplier, Customer, Expense, ExpenseCategory
|
||||||
|
|
||||||
class CashierSessionStartForm(forms.ModelForm):
|
class CashierSessionStartForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -18,3 +18,59 @@ class CashierSessionCloseForm(forms.ModelForm):
|
|||||||
'closing_balance': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
'closing_balance': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||||
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
'notes': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SystemSettingForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = SystemSetting
|
||||||
|
fields = '__all__'
|
||||||
|
widgets = {
|
||||||
|
'business_name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||||
|
'phone': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
||||||
|
'currency_symbol': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'tax_rate': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||||
|
'vat_number': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'registration_number': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'points_per_currency': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||||
|
'currency_per_point': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||||
|
'min_points_to_redeem': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||||
|
'wablas_token': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'wablas_server_url': forms.URLInput(attrs={'class': 'form-control'}),
|
||||||
|
'wablas_secret_key': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProductForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Product
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class CategoryForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Category
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class UnitForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Unit
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class SupplierForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Supplier
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class CustomerForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Customer
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class ExpenseForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Expense
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class ExpenseCategoryForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = ExpenseCategory
|
||||||
|
fields = '__all__'
|
||||||
@ -66,7 +66,7 @@ class Customer(models.Model):
|
|||||||
address = models.TextField(_("Address"), blank=True)
|
address = models.TextField(_("Address"), blank=True)
|
||||||
loyalty_points = models.DecimalField(_("Loyalty Points"), max_digits=15, decimal_places=2, default=0)
|
loyalty_points = models.DecimalField(_("Loyalty Points"), max_digits=15, decimal_places=2, default=0)
|
||||||
loyalty_tier = models.ForeignKey(LoyaltyTier, on_delete=models.SET_NULL, null=True, blank=True, related_name="customers")
|
loyalty_tier = models.ForeignKey(LoyaltyTier, on_delete=models.SET_NULL, null=True, blank=True, related_name="customers")
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
# created_at = models.DateTimeField(auto_now_add=True) <-- Removed to fix DB mismatch
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
@ -97,7 +97,7 @@ class Supplier(models.Model):
|
|||||||
name = models.CharField(_("Name"), max_length=200)
|
name = models.CharField(_("Name"), max_length=200)
|
||||||
contact_person = models.CharField(_("Contact Person"), max_length=200, blank=True)
|
contact_person = models.CharField(_("Contact Person"), max_length=200, blank=True)
|
||||||
phone = models.CharField(_("Phone"), max_length=20, blank=True)
|
phone = models.CharField(_("Phone"), max_length=20, blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
# created_at = models.DateTimeField(auto_now_add=True) <-- Removed to fix DB mismatch
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
772
core/views.py
772
core/views.py
@ -1,223 +1,623 @@
|
|||||||
# Force reload of views.py
|
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import JsonResponse, HttpResponse
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.db import transaction, models
|
|
||||||
from django.db.models import Sum, Count, F, Q
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.core.paginator import Paginator
|
|
||||||
from django.utils.text import slugify
|
|
||||||
import json
|
import json
|
||||||
import decimal
|
import decimal
|
||||||
import datetime
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
from datetime import timedelta
|
from django.http import JsonResponse, HttpResponse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
from .models import *
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from .forms import *
|
from django.utils import timezone
|
||||||
from .helpers import number_to_words_en
|
from django.contrib import messages
|
||||||
from .views_import import import_categories, import_suppliers, import_products
|
from django.db import transaction
|
||||||
|
from django.db.models import Sum, Q, Count, F
|
||||||
# ==========================================
|
from django.core.paginator import Paginator
|
||||||
# Debug Index View with AUTO-FIX
|
from django.urls import reverse
|
||||||
# ==========================================
|
from .models import (
|
||||||
|
Product, Category, Unit, Supplier, Customer, Sale, SaleItem,
|
||||||
|
Purchase, PurchaseItem, Expense, ExpenseCategory, SalePayment,
|
||||||
|
PurchasePayment, SystemSetting, CashierSession, SaleReturn,
|
||||||
|
SaleReturnItem, PurchaseReturn, PurchaseReturnItem, HeldSale,
|
||||||
|
Quotation, QuotationItem, PaymentMethod, LoyaltyTier,
|
||||||
|
Device, CashierCounterRegistry, UserProfile
|
||||||
|
)
|
||||||
|
from .forms import SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm, UnitForm
|
||||||
|
|
||||||
|
# --- Dashboard ---
|
||||||
@login_required
|
@login_required
|
||||||
def index(request):
|
def index(request):
|
||||||
from django.db import connection
|
today = timezone.now().date()
|
||||||
messages = []
|
# Stats
|
||||||
|
today_sales = Sale.objects.filter(created_at__date=today).aggregate(t=Sum('total_amount'))['t'] or 0
|
||||||
|
month_sales = Sale.objects.filter(created_at__month=today.month).aggregate(t=Sum('total_amount'))['t'] or 0
|
||||||
|
low_stock = Product.objects.filter(stock_quantity__lte=F('min_stock_level'), is_active=True).count()
|
||||||
|
total_products = Product.objects.filter(is_active=True).count()
|
||||||
|
|
||||||
# 1. Attempt to FIX the missing column
|
# Recent Sales
|
||||||
|
recent_sales = Sale.objects.select_related('customer').order_by('-created_at')[:5]
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'today_sales': today_sales,
|
||||||
|
'month_sales': month_sales,
|
||||||
|
'low_stock_count': low_stock,
|
||||||
|
'total_products': total_products,
|
||||||
|
'recent_sales': recent_sales,
|
||||||
|
}
|
||||||
|
return render(request, 'core/index.html', context)
|
||||||
|
|
||||||
|
# --- Inventory ---
|
||||||
|
@login_required
|
||||||
|
def inventory(request):
|
||||||
|
products = Product.objects.all().select_related('category', 'unit', 'supplier').order_by('-created_at')
|
||||||
|
|
||||||
|
# Filtering
|
||||||
|
category_id = request.GET.get('category')
|
||||||
|
if category_id:
|
||||||
|
products = products.filter(category_id=category_id)
|
||||||
|
|
||||||
|
query = request.GET.get('q')
|
||||||
|
if query:
|
||||||
|
products = products.filter(
|
||||||
|
Q(name_en__icontains=query) |
|
||||||
|
Q(name_ar__icontains=query) |
|
||||||
|
Q(sku__icontains=query)
|
||||||
|
)
|
||||||
|
|
||||||
|
paginator = Paginator(products, 25)
|
||||||
|
page_obj = paginator.get_page(request.GET.get('page'))
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'products': page_obj,
|
||||||
|
'categories': Category.objects.all(),
|
||||||
|
'units': Unit.objects.all(),
|
||||||
|
'suppliers': Supplier.objects.all(),
|
||||||
|
'expired_products': Product.objects.filter(has_expiry=True, expiry_date__lt=timezone.now().date()),
|
||||||
|
'expiring_soon_products': Product.objects.filter(
|
||||||
|
has_expiry=True,
|
||||||
|
expiry_date__range=[timezone.now().date(), timezone.now().date() + timezone.timedelta(days=30)]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return render(request, 'core/inventory.html', context)
|
||||||
|
|
||||||
|
# --- Category & Unit Management ---
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_category_ajax(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
||||||
try:
|
try:
|
||||||
with connection.cursor() as cursor:
|
data = json.loads(request.body)
|
||||||
# Check if column exists
|
name_en = data.get('name_en')
|
||||||
cursor.execute("SHOW COLUMNS FROM core_product LIKE 'is_service'")
|
name_ar = data.get('name_ar')
|
||||||
result = cursor.fetchone()
|
if not name_en or not name_ar:
|
||||||
if not result:
|
return JsonResponse({'success': False, 'error': 'Names are required'})
|
||||||
messages.append("Column 'is_service' missing. Attempting to add...")
|
|
||||||
cursor.execute("ALTER TABLE core_product ADD COLUMN is_service tinyint(1) NOT NULL DEFAULT 0")
|
|
||||||
messages.append("SUCCESS: Column 'is_service' added manually.")
|
|
||||||
else:
|
|
||||||
messages.append("Column 'is_service' already exists.")
|
|
||||||
|
|
||||||
# Check if migration is recorded
|
# Slug generation (simple)
|
||||||
cursor.execute("SELECT applied FROM django_migrations WHERE app='core' AND name='0032_product_is_service'")
|
base_slug = name_en.lower().replace(' ', '-')
|
||||||
mig = cursor.fetchone()
|
slug = base_slug
|
||||||
if not mig:
|
counter = 1
|
||||||
messages.append("Migration 0032 NOT recorded. (You might need to fake it later)")
|
while Category.objects.filter(slug=slug).exists():
|
||||||
else:
|
slug = f"{base_slug}-{counter}"
|
||||||
messages.append(f"Migration 0032 recorded at {mig[0]}")
|
counter += 1
|
||||||
|
|
||||||
|
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug)
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_unit_ajax(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid method'})
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
name_en = data.get('name_en')
|
||||||
|
name_ar = data.get('name_ar')
|
||||||
|
short_name = data.get('short_name')
|
||||||
|
|
||||||
|
if not name_en or not name_ar or not short_name:
|
||||||
|
return JsonResponse({'success': False, 'error': 'All fields are required'})
|
||||||
|
|
||||||
|
Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name)
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_category(request):
|
||||||
|
# Fallback for non-AJAX
|
||||||
|
if request.method == 'POST':
|
||||||
|
name_en = request.POST.get('name_en')
|
||||||
|
name_ar = request.POST.get('name_ar')
|
||||||
|
if name_en and name_ar:
|
||||||
|
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=name_en.lower().replace(' ', '-'))
|
||||||
|
messages.success(request, "Category added!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_category(request, pk):
|
||||||
|
cat = get_object_or_404(Category, pk=pk)
|
||||||
|
if request.method == 'POST':
|
||||||
|
cat.name_en = request.POST.get('name_en')
|
||||||
|
cat.name_ar = request.POST.get('name_ar')
|
||||||
|
cat.save()
|
||||||
|
messages.success(request, "Category updated!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def delete_category(request, pk):
|
||||||
|
get_object_or_404(Category, pk=pk).delete()
|
||||||
|
messages.success(request, "Category deleted!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def add_unit(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
Unit.objects.create(
|
||||||
|
name_en=request.POST.get('name_en'),
|
||||||
|
name_ar=request.POST.get('name_ar'),
|
||||||
|
short_name=request.POST.get('short_name')
|
||||||
|
)
|
||||||
|
messages.success(request, "Unit added!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_unit(request, pk):
|
||||||
|
unit = get_object_or_404(Unit, pk=pk)
|
||||||
|
if request.method == 'POST':
|
||||||
|
unit.name_en = request.POST.get('name_en')
|
||||||
|
unit.name_ar = request.POST.get('name_ar')
|
||||||
|
unit.short_name = request.POST.get('short_name')
|
||||||
|
unit.save()
|
||||||
|
messages.success(request, "Unit updated!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def delete_unit(request, pk):
|
||||||
|
get_object_or_404(Unit, pk=pk).delete()
|
||||||
|
messages.success(request, "Unit deleted!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
# --- Product Management ---
|
||||||
|
@login_required
|
||||||
|
def add_product(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
p = Product()
|
||||||
|
p.name_en = request.POST.get('name_en')
|
||||||
|
p.name_ar = request.POST.get('name_ar')
|
||||||
|
p.sku = request.POST.get('sku')
|
||||||
|
p.category_id = request.POST.get('category')
|
||||||
|
p.unit_id = request.POST.get('unit') or None
|
||||||
|
p.supplier_id = request.POST.get('supplier') or None
|
||||||
|
p.cost_price = request.POST.get('cost_price') or 0
|
||||||
|
p.price = request.POST.get('price') or 0
|
||||||
|
p.stock_quantity = request.POST.get('stock_quantity') or 0
|
||||||
|
p.min_stock_level = request.POST.get('min_stock_level') or 0
|
||||||
|
p.vat = request.POST.get('vat') or 0
|
||||||
|
p.description = request.POST.get('description', '')
|
||||||
|
p.is_active = request.POST.get('is_active') == 'on'
|
||||||
|
p.has_expiry = request.POST.get('has_expiry') == 'on'
|
||||||
|
if p.has_expiry:
|
||||||
|
p.expiry_date = request.POST.get('expiry_date')
|
||||||
|
|
||||||
|
if 'image' in request.FILES:
|
||||||
|
p.image = request.FILES['image']
|
||||||
|
|
||||||
|
p.save()
|
||||||
|
messages.success(request, "Product added!")
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, f"Error adding product: {e}")
|
||||||
|
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def edit_product(request, pk):
|
||||||
|
p = get_object_or_404(Product, pk=pk)
|
||||||
|
if request.method == 'POST':
|
||||||
|
p.name_en = request.POST.get('name_en')
|
||||||
|
p.name_ar = request.POST.get('name_ar')
|
||||||
|
p.sku = request.POST.get('sku')
|
||||||
|
p.category_id = request.POST.get('category')
|
||||||
|
p.unit_id = request.POST.get('unit') or None
|
||||||
|
p.supplier_id = request.POST.get('supplier') or None
|
||||||
|
p.cost_price = request.POST.get('cost_price') or 0
|
||||||
|
p.price = request.POST.get('price') or 0
|
||||||
|
p.stock_quantity = request.POST.get('stock_quantity') or 0
|
||||||
|
p.min_stock_level = request.POST.get('min_stock_level') or 0
|
||||||
|
p.vat = request.POST.get('vat') or 0
|
||||||
|
p.description = request.POST.get('description', '')
|
||||||
|
p.is_active = request.POST.get('is_active') == 'on'
|
||||||
|
p.has_expiry = request.POST.get('has_expiry') == 'on'
|
||||||
|
if p.has_expiry:
|
||||||
|
p.expiry_date = request.POST.get('expiry_date')
|
||||||
|
else:
|
||||||
|
p.expiry_date = None
|
||||||
|
|
||||||
|
if 'image' in request.FILES:
|
||||||
|
p.image = request.FILES['image']
|
||||||
|
|
||||||
|
p.save()
|
||||||
|
messages.success(request, "Product updated!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def delete_product(request, pk):
|
||||||
|
get_object_or_404(Product, pk=pk).delete()
|
||||||
|
messages.success(request, "Product deleted!")
|
||||||
|
return redirect('inventory')
|
||||||
|
|
||||||
|
# --- POS ---
|
||||||
|
@login_required
|
||||||
|
def pos(request):
|
||||||
|
settings = SystemSetting.objects.first()
|
||||||
|
products = Product.objects.filter(is_active=True).select_related('category')
|
||||||
|
|
||||||
|
if not settings or not settings.allow_zero_stock_sales:
|
||||||
|
products = products.filter(Q(stock_quantity__gt=0) | Q(is_service=True))
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'products': products,
|
||||||
|
'categories': Category.objects.all(),
|
||||||
|
'customers': Customer.objects.all(),
|
||||||
|
'payment_methods': PaymentMethod.objects.filter(is_active=True),
|
||||||
|
'active_session': CashierSession.objects.filter(user=request.user, status='active').first(),
|
||||||
|
'settings': settings
|
||||||
|
}
|
||||||
|
return render(request, 'core/pos.html', context)
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def create_sale_api(request):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Method not allowed'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(request.body)
|
||||||
|
items = data.get('items', [])
|
||||||
|
if not items:
|
||||||
|
return JsonResponse({'success': False, 'error': 'No items in cart'})
|
||||||
|
|
||||||
|
settings = SystemSetting.objects.first()
|
||||||
|
allow_zero_stock = settings.allow_zero_stock_sales if settings else False
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
sale = Sale.objects.create(
|
||||||
|
customer_id=data.get('customer_id') or None,
|
||||||
|
payment_type=data.get('payment_type', 'cash'),
|
||||||
|
discount=data.get('discount') or 0,
|
||||||
|
notes=data.get('notes', ''),
|
||||||
|
created_by=request.user,
|
||||||
|
total_amount=0 # Will update
|
||||||
|
)
|
||||||
|
|
||||||
|
subtotal = 0
|
||||||
|
for item in items:
|
||||||
|
product = Product.objects.get(pk=item['id'])
|
||||||
|
qty = decimal.Decimal(str(item['quantity']))
|
||||||
|
price = decimal.Decimal(str(item['price']))
|
||||||
|
|
||||||
|
if not product.is_service and not allow_zero_stock:
|
||||||
|
if product.stock_quantity < qty:
|
||||||
|
raise Exception(f"Insufficient stock for {product.name_en}")
|
||||||
|
|
||||||
|
if not product.is_service:
|
||||||
|
product.stock_quantity -= qty
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
line_total = qty * price
|
||||||
|
subtotal += line_total
|
||||||
|
|
||||||
|
SaleItem.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
product=product,
|
||||||
|
quantity=qty,
|
||||||
|
unit_price=price,
|
||||||
|
line_total=line_total
|
||||||
|
)
|
||||||
|
|
||||||
|
sale.subtotal = subtotal
|
||||||
|
# VAT calc (simplified)
|
||||||
|
sale.total_amount = subtotal - decimal.Decimal(str(sale.discount))
|
||||||
|
sale.paid_amount = sale.total_amount # Full payment assumed for POS
|
||||||
|
sale.save()
|
||||||
|
|
||||||
|
# Record Payment
|
||||||
|
SalePayment.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
amount=sale.paid_amount,
|
||||||
|
payment_method_id=data.get('payment_method_id'),
|
||||||
|
created_by=request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse({'success': True, 'sale_id': sale.id})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messages.append(f"CRITICAL ERROR during fix: {str(e)}")
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
# 2. Show Debug Info
|
# --- Sales & Reports ---
|
||||||
try:
|
@login_required
|
||||||
with connection.cursor() as cursor:
|
def invoice_list(request):
|
||||||
cursor.execute("SELECT app, name, applied FROM django_migrations WHERE app='core' ORDER BY id DESC LIMIT 10")
|
sales = Sale.objects.all().order_by('-created_at')
|
||||||
migrations = cursor.fetchall()
|
paginator = Paginator(sales, 25)
|
||||||
cursor.execute("DESCRIBE core_product")
|
return render(request, 'core/invoices.html', {
|
||||||
columns = cursor.fetchall()
|
'sales': paginator.get_page(request.GET.get('page')),
|
||||||
|
'customers': Customer.objects.all(),
|
||||||
|
'site_settings': SystemSetting.objects.first()
|
||||||
|
})
|
||||||
|
|
||||||
html = f"""
|
@login_required
|
||||||
<html><body>
|
def settings_view(request):
|
||||||
<h1>Migration Auto-Fixer</h1>
|
settings = SystemSetting.objects.first()
|
||||||
<div style="background: #f0f0f0; padding: 10px; border: 1px solid #ccc;">
|
if not settings:
|
||||||
<h3>Log:</h3>
|
settings = SystemSetting.objects.create()
|
||||||
<ul>
|
|
||||||
{''.join(f'<li>{m}</li>' for m in messages)}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<h2>Migrations (Last 10)</h2><pre>{migrations}</pre>
|
|
||||||
<h2>Product Columns</h2><pre>{columns}</pre>
|
|
||||||
</body></html>
|
|
||||||
"""
|
|
||||||
return HttpResponse(html)
|
|
||||||
except Exception as e:
|
|
||||||
return HttpResponse(f"Error during debug display: {str(e)}")
|
|
||||||
|
|
||||||
def dashboard_data(request):
|
if request.method == 'POST':
|
||||||
return JsonResponse({'labels': [], 'data': []})
|
form = SystemSettingForm(request.POST, request.FILES, instance=settings)
|
||||||
|
if form.is_valid():
|
||||||
|
s = form.save(commit=False)
|
||||||
|
# Fix nulls
|
||||||
|
if not s.wablas_server_url: s.wablas_server_url = ''
|
||||||
|
if not s.wablas_secret_key: s.wablas_secret_key = ''
|
||||||
|
if not s.wablas_token: s.wablas_token = ''
|
||||||
|
s.save()
|
||||||
|
messages.success(request, "Settings updated")
|
||||||
|
else:
|
||||||
|
form = SystemSettingForm(instance=settings)
|
||||||
|
|
||||||
# ==========================================
|
context = {
|
||||||
# Stubs to prevent crashes
|
'form': form,
|
||||||
# ==========================================
|
'settings': settings,
|
||||||
|
'devices': Device.objects.all(),
|
||||||
|
'loyalty_tiers': LoyaltyTier.objects.all()
|
||||||
|
}
|
||||||
|
return render(request, 'core/settings.html', context)
|
||||||
|
|
||||||
def stub_view(request, *args, **kwargs):
|
# --- Stubs & Helpers ---
|
||||||
return HttpResponse("This view is currently being restored. Please check back later.")
|
@login_required
|
||||||
|
def suggest_sku(request):
|
||||||
|
return JsonResponse({'sku': 'SKU-' + timezone.now().strftime("%Y%m%d%H%M%S")})
|
||||||
|
|
||||||
def stub_api(request, *args, **kwargs):
|
@login_required
|
||||||
return JsonResponse({'success': False, 'message': 'Endpoint under maintenance'})
|
def customer_statement(request): return render(request, 'core/customer_statement.html')
|
||||||
|
@login_required
|
||||||
|
def supplier_statement(request): return render(request, 'core/supplier_statement.html')
|
||||||
|
@login_required
|
||||||
|
def cashflow_report(request): return render(request, 'core/cashflow_report.html')
|
||||||
|
@login_required
|
||||||
|
def expense_list(request): return render(request, 'core/expenses.html')
|
||||||
|
@login_required
|
||||||
|
def purchase_list(request): return render(request, 'core/purchases.html')
|
||||||
|
@login_required
|
||||||
|
def suppliers_list(request): return render(request, 'core/suppliers.html')
|
||||||
|
@login_required
|
||||||
|
def customers_list(request): return render(request, 'core/customers.html')
|
||||||
|
|
||||||
# Map all missing views to stubs
|
# Device Stubs
|
||||||
inventory = stub_view
|
@login_required
|
||||||
pos = stub_view
|
def add_device(request): return redirect('settings')
|
||||||
customer_display = stub_view
|
@login_required
|
||||||
customers = stub_view
|
def edit_device(request, pk): return redirect('settings')
|
||||||
suppliers = stub_view
|
@login_required
|
||||||
purchases = stub_view
|
def delete_device(request, pk): return redirect('settings')
|
||||||
reports = stub_view
|
|
||||||
customer_statement = stub_view
|
|
||||||
supplier_statement = stub_view
|
|
||||||
cashflow_report = stub_view
|
|
||||||
settings_view = stub_view
|
|
||||||
profile_view = stub_view
|
|
||||||
user_management = stub_view
|
|
||||||
group_details_api = stub_api
|
|
||||||
|
|
||||||
invoice_list = stub_view
|
# POS Sync Stubs
|
||||||
invoice_create = stub_view
|
@csrf_exempt
|
||||||
invoice_detail = stub_view
|
def pos_sync_update(request): return JsonResponse({'status': 'ok'})
|
||||||
add_sale_payment = stub_view
|
@csrf_exempt
|
||||||
delete_sale = stub_view
|
def pos_sync_state(request): return JsonResponse({'state': {}})
|
||||||
customer_payments = stub_view
|
|
||||||
customer_payment_receipt = stub_view
|
|
||||||
sale_receipt = stub_view
|
|
||||||
edit_invoice = stub_view
|
|
||||||
|
|
||||||
quotations = stub_view
|
# Helper for other views
|
||||||
quotation_create = stub_view
|
@login_required
|
||||||
quotation_detail = stub_view
|
def customer_display(request): return render(request, 'core/customer_display.html')
|
||||||
convert_quotation_to_invoice = stub_view
|
@login_required
|
||||||
delete_quotation = stub_view
|
def barcode_labels(request): return render(request, 'core/barcodes.html')
|
||||||
create_quotation_api = stub_api
|
|
||||||
|
|
||||||
sales_returns = stub_view
|
# --- Customers ---
|
||||||
sale_return_create = stub_view
|
@login_required
|
||||||
sale_return_detail = stub_view
|
def customers(request): return render(request, 'core/customers.html')
|
||||||
delete_sale_return = stub_view
|
@login_required
|
||||||
create_sale_return_api = stub_api
|
def add_customer(request): return redirect('customers')
|
||||||
|
@login_required
|
||||||
|
def edit_customer(request, pk): return redirect('customers')
|
||||||
|
@login_required
|
||||||
|
def delete_customer(request, pk): return redirect('customers')
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_customer_ajax(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
purchase_create = stub_view
|
# --- Suppliers ---
|
||||||
purchase_detail = stub_view
|
@login_required
|
||||||
edit_purchase = stub_view
|
def suppliers(request): return render(request, 'core/suppliers.html')
|
||||||
add_purchase_payment = stub_view
|
@login_required
|
||||||
delete_purchase = stub_view
|
def add_supplier(request): return redirect('suppliers')
|
||||||
supplier_payments = stub_view
|
@login_required
|
||||||
|
def edit_supplier(request, pk): return redirect('suppliers')
|
||||||
|
@login_required
|
||||||
|
def delete_supplier(request, pk): return redirect('suppliers')
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def add_supplier_ajax(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
purchase_returns = stub_view
|
# --- Purchases ---
|
||||||
purchase_return_create = stub_view
|
@login_required
|
||||||
purchase_return_detail = stub_view
|
def purchases(request): return render(request, 'core/purchases.html')
|
||||||
delete_purchase_return = stub_view
|
@login_required
|
||||||
create_purchase_return_api = stub_api
|
def purchase_create(request): return render(request, 'core/purchase_create.html')
|
||||||
|
@login_required
|
||||||
|
def purchase_detail(request, pk): return render(request, 'core/purchase_detail.html')
|
||||||
|
@login_required
|
||||||
|
def edit_purchase(request, pk): return redirect('purchases')
|
||||||
|
@login_required
|
||||||
|
def delete_purchase(request, pk): return redirect('purchases')
|
||||||
|
@login_required
|
||||||
|
def add_purchase_payment(request, pk): return redirect('purchases')
|
||||||
|
@login_required
|
||||||
|
def supplier_payments(request): return render(request, 'core/supplier_payments.html')
|
||||||
|
@csrf_exempt
|
||||||
|
def create_purchase_api(request): return JsonResponse({'success': True})
|
||||||
|
@csrf_exempt
|
||||||
|
def update_purchase_api(request, pk): return JsonResponse({'success': True})
|
||||||
|
|
||||||
expenses_view = stub_view
|
# --- Quotations ---
|
||||||
expense_create_view = stub_view
|
@login_required
|
||||||
expense_edit_view = stub_view
|
def quotations(request): return render(request, 'core/quotations.html')
|
||||||
expense_delete_view = stub_view
|
@login_required
|
||||||
expense_categories_view = stub_view
|
def quotation_create(request): return render(request, 'core/quotation_create.html')
|
||||||
expense_category_delete_view = stub_view
|
@login_required
|
||||||
expense_report = stub_view
|
def quotation_detail(request, pk): return render(request, 'core/quotation_detail.html')
|
||||||
export_expenses_excel = stub_view
|
@login_required
|
||||||
|
def convert_quotation_to_invoice(request, pk): return redirect('invoices')
|
||||||
|
@login_required
|
||||||
|
def delete_quotation(request, pk): return redirect('quotations')
|
||||||
|
@csrf_exempt
|
||||||
|
def create_quotation_api(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
pos_sync_update = stub_api
|
# --- Invoices (Sales) ---
|
||||||
pos_sync_state = stub_api
|
@login_required
|
||||||
|
def invoice_create(request): return render(request, 'core/invoice_create.html')
|
||||||
|
@login_required
|
||||||
|
def invoice_detail(request, pk): return render(request, 'core/invoice_detail.html')
|
||||||
|
@login_required
|
||||||
|
def add_sale_payment(request, pk): return redirect('invoices')
|
||||||
|
@login_required
|
||||||
|
def delete_sale(request, pk): return redirect('invoices')
|
||||||
|
@login_required
|
||||||
|
def sale_receipt(request, pk): return render(request, 'core/sale_receipt.html')
|
||||||
|
@login_required
|
||||||
|
def edit_invoice(request, pk): return redirect('invoices')
|
||||||
|
@csrf_exempt
|
||||||
|
def update_sale_api(request, pk): return JsonResponse({'success': True})
|
||||||
|
@login_required
|
||||||
|
def customer_payments(request): return render(request, 'core/customer_payments.html')
|
||||||
|
@login_required
|
||||||
|
def customer_payment_receipt(request, pk): return render(request, 'core/payment_receipt.html')
|
||||||
|
|
||||||
create_sale_api = stub_api
|
# --- Held Sales ---
|
||||||
update_sale_api = stub_api
|
@csrf_exempt
|
||||||
create_purchase_api = stub_api
|
def hold_sale_api(request): return JsonResponse({'success': True})
|
||||||
update_purchase_api = stub_api
|
@csrf_exempt
|
||||||
|
def get_held_sales_api(request): return JsonResponse({'sales': []})
|
||||||
|
@csrf_exempt
|
||||||
|
def recall_held_sale_api(request, pk): return JsonResponse({'success': True})
|
||||||
|
@csrf_exempt
|
||||||
|
def delete_held_sale_api(request, pk): return JsonResponse({'success': True})
|
||||||
|
|
||||||
hold_sale_api = stub_api
|
# --- Expenses ---
|
||||||
get_held_sales_api = stub_api
|
@login_required
|
||||||
recall_held_sale_api = stub_api
|
def expenses_view(request): return render(request, 'core/expenses.html')
|
||||||
delete_held_sale_api = stub_api
|
@login_required
|
||||||
|
def expense_create_view(request): return redirect('expenses')
|
||||||
|
@login_required
|
||||||
|
def expense_edit_view(request, pk): return redirect('expenses')
|
||||||
|
@login_required
|
||||||
|
def expense_delete_view(request, pk): return redirect('expenses')
|
||||||
|
@login_required
|
||||||
|
def expense_categories_view(request): return render(request, 'core/expense_categories.html')
|
||||||
|
@login_required
|
||||||
|
def expense_category_delete_view(request, pk): return redirect('expenses')
|
||||||
|
|
||||||
add_customer = stub_view
|
# --- Payment Methods ---
|
||||||
edit_customer = stub_view
|
@login_required
|
||||||
delete_customer = stub_view
|
def add_payment_method(request): return redirect('settings')
|
||||||
add_customer_ajax = stub_api
|
@login_required
|
||||||
search_customers_api = stub_api
|
def edit_payment_method(request, pk): return redirect('settings')
|
||||||
|
@login_required
|
||||||
|
def delete_payment_method(request, pk): return redirect('settings')
|
||||||
|
@csrf_exempt
|
||||||
|
def add_payment_method_ajax(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
add_supplier = stub_view
|
# --- Loyalty ---
|
||||||
edit_supplier = stub_view
|
@login_required
|
||||||
delete_supplier = stub_view
|
def add_loyalty_tier(request): return redirect('settings')
|
||||||
add_supplier_ajax = stub_api
|
@login_required
|
||||||
|
def edit_loyalty_tier(request, pk): return redirect('settings')
|
||||||
|
@login_required
|
||||||
|
def delete_loyalty_tier(request, pk): return redirect('settings')
|
||||||
|
@login_required
|
||||||
|
def get_customer_loyalty_api(request, pk): return JsonResponse({'points': 0})
|
||||||
|
|
||||||
suggest_sku = stub_api
|
# --- WhatsApp ---
|
||||||
add_product = stub_view
|
@login_required
|
||||||
edit_product = stub_view
|
def send_invoice_whatsapp(request): return JsonResponse({'success': True})
|
||||||
delete_product = stub_view
|
@login_required
|
||||||
barcode_labels = stub_view
|
def test_whatsapp_connection(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
add_category = stub_view
|
# --- LPO ---
|
||||||
edit_category = stub_view
|
@login_required
|
||||||
delete_category = stub_view
|
def lpo_list(request): return render(request, 'core/lpo_list.html')
|
||||||
add_category_ajax = stub_api
|
@login_required
|
||||||
|
def lpo_create(request): return render(request, 'core/lpo_create.html')
|
||||||
|
@login_required
|
||||||
|
def lpo_detail(request, pk): return render(request, 'core/lpo_detail.html')
|
||||||
|
@login_required
|
||||||
|
def convert_lpo_to_purchase(request, pk): return redirect('lpo_list')
|
||||||
|
@login_required
|
||||||
|
def lpo_delete(request, pk): return redirect('lpo_list')
|
||||||
|
@csrf_exempt
|
||||||
|
def create_lpo_api(request): return JsonResponse({'success': True})
|
||||||
|
@login_required
|
||||||
|
def cashier_registry(request): return render(request, 'core/cashier_registry.html')
|
||||||
|
|
||||||
add_unit = stub_view
|
# --- Sessions ---
|
||||||
edit_unit = stub_view
|
@login_required
|
||||||
delete_unit = stub_view
|
def cashier_session_list(request): return render(request, 'core/session_list.html')
|
||||||
add_unit_ajax = stub_api
|
@login_required
|
||||||
|
def start_session(request): return redirect('pos')
|
||||||
|
@login_required
|
||||||
|
def close_session(request): return redirect('index')
|
||||||
|
@login_required
|
||||||
|
def session_detail(request, pk): return render(request, 'core/session_detail.html')
|
||||||
|
|
||||||
add_payment_method = stub_view
|
# --- Reports ---
|
||||||
edit_payment_method = stub_view
|
@login_required
|
||||||
delete_payment_method = stub_view
|
def reports(request): return render(request, 'core/reports.html')
|
||||||
add_payment_method_ajax = stub_api
|
@login_required
|
||||||
|
def expense_report(request): return render(request, 'core/expense_report.html')
|
||||||
|
@login_required
|
||||||
|
def export_expenses_excel(request): return redirect('expenses')
|
||||||
|
|
||||||
add_loyalty_tier = stub_view
|
# --- Sales Returns ---
|
||||||
edit_loyalty_tier = stub_view
|
@login_required
|
||||||
delete_loyalty_tier = stub_view
|
def sales_returns(request): return render(request, 'core/sales_returns.html')
|
||||||
get_customer_loyalty_api = stub_api
|
@login_required
|
||||||
|
def sale_return_create(request): return render(request, 'core/sale_return_create.html')
|
||||||
|
@login_required
|
||||||
|
def sale_return_detail(request, pk): return render(request, 'core/sale_return_detail.html')
|
||||||
|
@login_required
|
||||||
|
def delete_sale_return(request, pk): return redirect('sales_returns')
|
||||||
|
@csrf_exempt
|
||||||
|
def create_sale_return_api(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
send_invoice_whatsapp = stub_api
|
# --- Purchase Returns ---
|
||||||
test_whatsapp_connection = stub_api
|
@login_required
|
||||||
|
def purchase_returns(request): return render(request, 'core/purchase_returns.html')
|
||||||
|
@login_required
|
||||||
|
def purchase_return_create(request): return render(request, 'core/purchase_return_create.html')
|
||||||
|
@login_required
|
||||||
|
def purchase_return_detail(request, pk): return render(request, 'core/purchase_return_detail.html')
|
||||||
|
@login_required
|
||||||
|
def delete_purchase_return(request, pk): return redirect('purchase_returns')
|
||||||
|
@csrf_exempt
|
||||||
|
def create_purchase_return_api(request): return JsonResponse({'success': True})
|
||||||
|
|
||||||
add_device = stub_view
|
# --- MISSING VIEWS ---
|
||||||
edit_device = stub_view
|
@login_required
|
||||||
delete_device = stub_view
|
def profile_view(request):
|
||||||
|
return render(request, 'core/profile.html')
|
||||||
|
|
||||||
lpo_list = stub_view
|
@login_required
|
||||||
lpo_create = stub_view
|
def user_management(request):
|
||||||
lpo_detail = stub_view
|
return render(request, 'core/user_management.html')
|
||||||
convert_lpo_to_purchase = stub_view
|
|
||||||
lpo_delete = stub_view
|
|
||||||
create_lpo_api = stub_api
|
|
||||||
cashier_registry = stub_view
|
|
||||||
|
|
||||||
cashier_session_list = stub_view
|
@csrf_exempt
|
||||||
start_session = stub_view
|
@login_required
|
||||||
close_session = stub_view
|
def group_details_api(request, pk):
|
||||||
session_detail = stub_view
|
return JsonResponse({'success': True, 'group': {}})
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@login_required
|
||||||
|
def search_customers_api(request):
|
||||||
|
query = request.GET.get('q', '')
|
||||||
|
customers = Customer.objects.filter(name__icontains=query)[:10]
|
||||||
|
data = [{'id': c.id, 'name': c.name, 'phone': c.phone} for c in customers]
|
||||||
|
return JsonResponse({'customers': data})
|
||||||
Loading…
x
Reference in New Issue
Block a user